DayView demo – DSP#19

Jak pisałem poprzednim razem udało się już ukończyć bibliotekę, wszystko co chciałem zaimplementować udało się wdrożyć. Nie mniej przydała by się jakaś mała aplikacja która pozwoliła by pokazać jej możliwości. To właśnie taki jest kolejny krok i o tym dzisiaj napiszę.

Na początek kilka wymagań, chcę żeby aplikacja:

  • prezentowała możliwości API
  • posiadała odnośnik do źródeł biblioteki
  • oczywiście wyświetlała bibliotekę, przynajmniej w dwóch rozmiarach

Udało się tym wymaganiom sprostać, taki jest efekt końcowy:

Aplikacja będzie dostępna w sklepie Play o tutaj, a także na stronie githubowej. Ale to to wszystko dopiero jak uda mi się udostępnić bibliotekę, oraz napisać README, trochę ją wypromować. Tak wygląda kod źródłowy dla tego małego demka:

class MainActivity : AppCompatActivity() {

    var dayView: DayView? = null
    var dayView2: DayView? = null
    var applyBtn: Button? = null
    var borderBtn: Button? = null
    var backgroundBtn: Button? = null
    var textBtn: Button? = null
    var barBtn: Button? = null
    var dayEt: EditText? = null
    var monthEt: EditText? = null
    var borderColor: Int = Color.BLACK
    var backgroundColor: Int = Color.WHITE
    var textColor: Int = Color.BLACK
    var barColor: Int = Color.BLUE
    var date: Date = Date()
    var day: Int = 1
    var month: Int = 1
    val year: Int = 2016
    var url: TextView? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        dayView = findViewById(R.id.dayView) as DayView
        dayView2 = findViewById(R.id.dayView2) as DayView
        applyBtn = findViewById(R.id.apply_btn) as Button
        borderBtn = findViewById(R.id.borderColor_btn) as Button
        backgroundBtn = findViewById(R.id.backgroundColor_btn) as Button
        textBtn = findViewById(R.id.textColor_btn) as Button
        barBtn = findViewById(R.id.barColor_btn) as Button
        dayEt = findViewById(R.id.day_et) as EditText
        monthEt = findViewById(R.id.month_et) as EditText
        url = findViewById(R.id.url_tv) as TextView
        val toolbar = findViewById(R.id.toolbar) as Toolbar
        setSupportActionBar(toolbar)

        val pattern = Pattern.compile("https://github.com/migellal/DayView")
        Linkify.addLinks(url, pattern, "")

        applyBtn!!.setOnClickListener {
            if (dayEt!!.text != null && !dayEt!!.text.isEmpty()
                    && monthEt!!.text != null && !monthEt!!.text.isEmpty()) {
                day = dayEt!!.text.toString().toInt()
                month = monthEt!!.text.toString().toInt()
                month -= 1
            }
            date = Date(year, month, day)
            dayView!!.date = date
            dayView!!.borderColor = borderColor
            dayView!!.cardBackgroundColor = backgroundColor
            dayView!!.textColor = textColor
            dayView!!.barColor = barColor
            dayView2!!.date = date
            dayView2!!.borderColor = borderColor
            dayView2!!.cardBackgroundColor = backgroundColor
            dayView2!!.textColor = textColor
            dayView2!!.barColor = barColor
        }

        borderBtn!!.setOnClickListener {
            colorPicker(borderBtn as Button)
        }

        backgroundBtn!!.setOnClickListener {
            colorPicker(backgroundBtn as Button)
        }

        textBtn!!.setOnClickListener {
            colorPicker(textBtn as Button)
        }

        barBtn!!.setOnClickListener {
            colorPicker(barBtn as Button)
        }
    }

    private fun colorPicker(btn: Button) {
        ColorOMaticDialog.Builder()
                .initialColor(Color.BLACK)
                .colorMode(ColorMode.RGB)
                .indicatorMode(IndicatorMode.HEX)
                .onColorSelected {
                    btn.setBackgroundColor(it)
                    if (btn == borderBtn)
                        borderColor = it
                    else if (btn == backgroundBtn)
                        backgroundColor = it
                    else if (btn == textBtn)
                        textColor = it
                    else
                        barColor = it
                }
                .create()
                .show(supportFragmentManager, "ColorOMaticDialog")
    }

}

Kilka uwag do kodu, użyłem biblioteki color-o-matic do wyświetlania menu wyboru koloru. Android nie ma jej dostępnej natywnie, stąd mogłem albo użyć gotowej, albo pisać samemu. Dodatkowo kod listenerów w Kotlinie wygląda całkiem fajnie. Tzn, jeśli znajduje się tam tylko jedna metoda (np onClick()) to nie zapisujemy jej, tylko kod umieszczamy w nawiasach klamrowych. Kolejną ciekawostką jest słówko kluczowe it, które odnosi się do obiektu z którego korzystamy w danej metodzie. Na przykładzie implementacji color-o-matic, listener onColorSelected posiadał metodę onSelect, albo podobną. Nie trzeba jej wpisywać, żeby dostać się do parametru który jest tam podany, wystarczy użyć wspomnianego słówka kluczowego. Warto też wspomnieć, że do poziomego ułożenia telefonu został dopisany nowy layout dla demo, ponieważ domyślne ułożenie po obrocie mi się nie podobało. Poprawiłem też mały błąd, który powodował, że jeśli nie wstawiliśmy daty nic nie było wyświetlane, założeniem było, żeby wyświetlała się wtedy dzisiejsza data i tak teraz jest.

To tyle jeśli chodzi o demko, nie ma się tutaj co rozpisywać, najważniejsze, żeby pokazywało co biblioteka potrafi i wydaje mi się, że to zadanie zostało spełnione. Wszystkie zmiany zostały zaccomitowane i znajdują się tutaj. A następnym krokiem jest publikacja biblioteki.

Finalna wersja kartki z kalendarza – DSP#18

Udało się! Doszedłem do momentu w którym biblioteka została ukończona. Może trudno w to uwierzyć, ale z każdym postem dotyczącym aplikacji dodawałem tylko kilka linii kodu. Dlatego posty na temat biblioteki zebrane razem mogą stanowić całkiem ciekawy tutorial, gdzie wszystko jest pokazane, łącznie z moim potknięciami. Ale do rzeczy, tak wygląda finalna (na ten moment) wersja kartki z kalendarza.

Finalna wersja kartki z kalendarza

Piszę na ten moment, ponieważ możliwe, że będzie się rozwijała, albo, że pojawią się jakieś błędy o których nie mam pojęcia. Bądź co bądź wydaje mi się, że dużo można jeszcze tutaj dorobić. Np przycisk do przesuwania daty w lewo, w prawo, możliwość wyświetlenia roku, możliwość poszerzenia ramki, etc. Noo ale do rzeczy, oto jak wygląda kodzik który dopisałem tym razem.

    private fun drawTextCenter(canvas: Canvas?) {
        val shortMonthName = month.length <= 4
        val x = canvas!!.width.toFloat() / 2
        val y = (endY - barY) / 5
        textDayPaint.textSize = 2 * y
        canvas.drawText(day, x, barY + (2.5 * y).toFloat(), textDayPaint)
        if (shortMonthName) {
            textMonthPaint.textSize = y
            canvas.drawText(month, x, barY + (3.5 * y).toFloat(), textMonthPaint)
        } else {
            textMonthPaint.textSize = y / 2
            canvas.drawText(month, x, barY + (3.1 * y).toFloat(), textMonthPaint)
        }
    }

    private fun getMonth(month: Int): String {
        return DateFormatSymbols().months[month - 1]
    }

Mamy tutaj dwie metody, jedna służy do rysowania tekstu na środku ekranu, druga do pobierania nazwy miesiąca. Może najpierw o tej drugiej, jak widać wpada tutaj parametr int, na tej podstawie z klasy DateFormatSymbols jest pobierana nazwa według aktualnego Locale. Co ciekawe, Android ma swoją wersję tej metody, ale pojawiła się ona dopiero niedawno, dlatego nie zgadza się ze wszystkimi API które obsługuje. Główna metoda którą chciałem omówić jest nieco dłuższa, w niej wyliczana jest pozycja napisów, oraz są one drukowane na ekranie. Wyliczenie x to po prostu podzielenie rozmiaru płótna na pół, ustawienie formatowania tekstu, żeby wyświetlał się na środku, no i można wrzucać tekst. Z y problem jest nieco większy, tzn potrzeba nam dwóch wysokości, jednej dla dużej cyfry, drugiej dla napisu. Dlatego też dzielę pozostałe miejsce na 5. A następnie umieszczam cyfrę, oraz miesiąc na wysokości, która wizualnie najbardziej mi odpowiada (stąd te 3.1, 3.5 jako mnożniki). Znajduje się tam też jeden if, który na podstawie długości tekstu w miesiącu sprawdza na jaki rozmiar ustawić czcionkę dla miesiąca. Dla krótkich miesięcy wygląda to w ten sposób.

Finalna wersja kartki z kalendarza

To by było na tyle, finalna wersja biblioteki zrobiona. Noo dobra to nie jest tak do końca nomen omen koniec. Do zrobienia mam jeszcze małą aplikację demo gdzie będzie można sobie sobie wybrać kolor i datę i zobaczyć jak to wygląda (ta aplikacja trafi do sklepu Play). Do tego trzeba jeszcze zarejestrować bibliotekę tak, żeby była ogólnodostępna, oraz trochę ją wypromować, uzupełnić README na Githubie. A kod z związany z dzisiejszym wpisem dostępny jest tutaj.

W jaki sposób NFC może zautomatyzować nasze życie – DSP#17

Lubimy automatyzację. Po co przeklikiwać się przez formatki, skoro coś może dziać się samo, prawda? Jedną z rzeczy które mogą zautomatyzować nasze życie są tagi NFC. To takie fajne hardwarowe guziczki, które mogą zrobić to co chcemy. Technologia ma już kilka lat, jest bardzo przydatna, ale jednak nie stała się bardzo popularna, stąd właśnie pomysł na post, który trochę ją przybliża. Ponieważ mało kto wie, ale jest również prosta w użyciu, oraz tania – telefon z Androidem posiadającym NFC oraz TAGi, których koszt to kilka złotych (a dostępne są nawet na popularnym profilu aukcyjnym) – to wszystko czego potrzeba (iOS posiada NFC, niestety można nim tylko płacić, nie da się obsługiwać tagów, info stąd).

Breloki NFC

Breloki NFC

Technologia

Tagi NFC wyewoluowały z kart RFID które można kojarzyć np. z otwierania drzwi do biura 🙂 Chodzi mi o te karty które dostaje każdy pracownik i dzięki którym przestarzałe klamki nie muszą już być używane i podobno wpływa to na bezpieczeństwo. Nie będę się tutaj skupiał na specyfikacji technicznej i historii, ponieważ informacje o tym można bez problemu znaleźć w sieci. Tylko kilka najistotniejszych informacji.

  1. NFC nie jest wstecznie kompatybilne, czyli urządzeniem odczytującym NFC nie odczytasz, nie nagrasz RFID.
  2. Tagi mogą być odblokowane do zapisu, bądź zablokowane. Telefonem z Androidem możemy odczytać, nagrać, a także zablokować tag, ale nie da rady go później odblokować.
  3. Urządzenia korzystające dzielimy na dwie podstawowe grupy, aktywne oraz pasywne. Różnica między nimi jest taka, że pasywne nie posiadają żadnego zasilania, dlatego nie możliwa jest komunikacja na linii pasywne – pasywne, wszystkie inne drogi są jak najbardziej otwarte.
  4. Karty płatnicze również korzystają z technologii NFC, tzn, że można je odczytać! Ale zanim założymy czapeczkę z folii aluminiowej (oczywiście na naszą kartę) warto zdawać sobie sprawę z tego, że takiej odczytanej karty nie da się potem użyć, ponieważ wewnątrz nich znajduje się jeszcze jeden układ scalony który jest odpytywany – zabezpieczenie hardwarowe. W związku z tym możliwy jest tylko atak zwany przedłużeniem terminala (szczegóły tutaj). Nie mniej, jeśli przyłożysz telefon do swojej karty płatniczej, a telefon ma włączone NFC usłyszysz charakterystyczny dźwięk podłączenia. I jeśli ktoś zaszył w swojej aplikacji tą bibliotekę to co nie co może się dowiedzieć.
Nieco inne breloki NFC

Nieco inne breloki NFC

Konfiguracja

Wystarczy tej teoretycznej wiedzy, przejdźmy do działań. Ja osobiście korzystam z aplikacji Trigger, do obsługi NFC, ale sklep Play pełen jest aplikacji tego typu. ja, jak wiadomo – programuję na Androida. I tam czasem potrzeba aby ekran był cały czas włączony (ponieważ jeśli debuguje się jakąś aplikację, to jeśli telefon przygasi ekran, to uruchomi metody systemowe związane z eventem i coś się psuje).  Można głaskać telefon co 30 sekund, można wejść w ustawienia -> ekran -> wygaszanie i wybrać opcję 30 minut. Ale można też skonfigurować taga, który ustawi nam odpowiednie opcje wygaszenia. Aby to zrobić uruchamiam aplikację Trigger (proponuję od razu uruchomić triala, wystarczy do skonfigurowania wielu rzeczy, a jeśli się spodoba, a nie zdążymy zrobić wszystkiego co chcieliśmy to koszt aplikacji – 15pln), uruchamiam opcję Create a task, jako trigger wybieram NFC. Następnie znajduje się ekran specyfikujący kiedy ma działać dana akcja, w tym przypadku to jest bez znaczenia. Klikamy done i na następnym ekranie wybieramy akcję.

nfc

Warto zauważyć, że znajduje się tam też opcja Tasker -> run Task. Tasker to narzędzie do automatyzacji totalnej jeśli chodzi o Androida, jeśli nie znane to bardzo polecam, oferuje ogrom możliwości. Mnie osobiście interesuje Display -> Timeout, a także Messages -> Show Toast. Więc zaznaczam i przechodzę dalej. Wyskakuje okienko konfiguracji, wybieram opcję nigdy nie wyłączaj ekranu, oraz wpisuję wiadomość w Toast. Nadaję nazwę taska, klikam opcję dalej i mogę dodać kolejne działanie, co w tym przypadku mi się przyda, bo chcę, aby po ponownym zeskanowaniu taga ekran wracał do prawidłowych ustawień. Więc tworzę taki setup. Wyskakuje okienko nagraj. Podsuwamy taga z tyłu telefonu, charakterystyczny dźwięk i… nagrane. To tyle, wszystko gotowe, działa, można pracować. W ustawieniach aplikacji można jeszcze wyłączyć powiadomienie na pasku. Warto też tutaj zauważyć, że nie jako rozszerzyliśmy możliwości swojego telefonu, ponieważ w ustawieniach nie było możliwości nie wyłączaj ekranu w ogóle.

nfc

Wykorzystanie

Dobrze wiedzieć, że utworzoną w ten sposób instrukcję da radę odczytać tylko telefon mający zainstalowaną aplikację Trigger. Innymi słowy, jeśli zmieniacie telefon, wystarczy ponowna instalacja aplikacji, aby wszystko śmigało, schematy w razie czego można odczytać z tagów i edytować. Jednak istnieją tagi które nie wymagają instalacji tej aplikacji. Mogą przechowywać np dane kontaktowe, albo adresy email. Mogą otwierać stronę www, otwierać tworzenie wiadomości email (dyrektywa mail:to). Mam takiego przypiętego do kluczy, zamiast dyktować adres swojej strony czasem zbliżam go do czyjegoś telefonu i strona się otwiera.

nfc

Instrukcja przedstawiona powyżej dotyczyła tylko przełączania się pomiędzy czasem podświetlenia ekranu, ale nie trudno wyobrazić sobie o wiele więcej scenariuszy:

  • Włączanie WIFI, włączanie bluetooth, wyłączanie dzwonków i wyświetlanie powiadomienia „Witaj w domu mistrzu” po odczytaniu tagu przy drzwiach wejściowych, odwrotne kombo przy drugim odczytaniu (podczas wychodzenia), miałem nawet kiedyś stworzony taki tag.
  • Przywieszony przy łóżku włączający budzik i tryb nocny (np minimalne podświetlenie ekranu).
  • Przy miejscu pracy który włącza firmową pocztę i firmowy zestaw aplikacji.
  • Który znajduje się przy kluczach i aktywujemy przed bieganiem i uruchamia nam aplikację muzyczną, endomondo i co tam jeszcze potrzeba

Tak naprawdę każdą powtarzalną czynność da się otagować, aby nie musieć się przez nią przeklikiwać, tylko móc użyć zewnętrznego, hardwarowego wywołania.

naklejki NFC

naklejki NFC

Konkurs

Mam nadzieję, że macie już mnóstwo pomysłów jak można by wykorzystać takie tagi. Dlatego proponuję konkurs, rozlosuję zestaw 5szt. (do wyboru naklejek, lub breloków) wśród osób, które udostępnią ten wpis, oraz polubią moją stronę. Konkurs trwa od daty publikacji wpisu do 8 maja do godziny 23:59. Potem mogę pomóc w konfiguracji pod dowolne zadania, umowa stoi? 😉

Podsumowanie

Jak widać NFC może nam bardzo pomóc każdego dnia. Technologia jest trochę zapomniana, tym większy efekt WOW, kiedy przyłożymy komuś brelok do telefonu i powiemy niczym hakerzy z „Mission Impossible”: „Mój telefon znajduje się już w Twoich kontaktach”. A to tylko jedna możliwość, bo tak naprawdę możemy z tego standardu wyciągnąć bardzo, bardzo dużo.

Co jest trudnego w narysowaniu dwóch zaokrąglonych rogów? – DSP#16

Dzisiaj skupimy się tylko i wyłącznie na rogach w kalendarzu. Tak konkretnie, to na dwóch, znajdujących się u góry, półokrągłych.. Nie brzmi skomplikowanie, prawda? Wbrew pozorom, sprawiło mi naprawdę sporo problemu. Ale po kolei.

Te rogi były przedostatnią rzeczą którą mamy do zrobienia (ostatnią są napisy z aktualną datą), najpierw wybór rozwiązania, czyli zajrzyjmy do klasy Path, jakie oferuje sposoby rysowania kątów. Spora część zawartych tam metod doszła w API21, a aplikacja wspiera także wcześniejsze (od API15), po analizie okazało się, że najlepiej będzie zrobić to ręcznie, korzystając z metody cubicTo. No i się zaczęło 😉

Pierwsza próba wyglądała obiecująco:

path.cubicTo(startLeft, startBar, width/2, startTop, width+1, startBar)

Co jest trudnego w narysowaniu dwóch zaokrąglonych rogów?

Parametry które po kolei przyjmuje ta funkcja:

  • startowyX
  • startowyY
  • środkowyX
  • środkowyY
  • końcowyX
  • końcowyY

Ok, czyli dwa pierwsze parametry to miejsce startu rysowania, dwa środkowe to punkt odniesienia, dwa końcowe, to miejsce gdzie funkcja się kończy. Nie brzmi najgorzej.

Podejście pierwsze

Mój plan był taki:

  1. Po lewej stronie punkt styku pomiędzy obramowaniem a wysokością paska
  2. Wartość stała na wysokości paska i lewa strona
  3. Krzywa do wartości stałej na górnym pasku, środkowy parametr to róg kalendarza
  4. Prosta do drugiej wartości stałej na pasku
  5. Krzywa do wartości stałej na wysokości paska i prawej strony, prawy róg kalendarza to środkowy parametr
  6. Prosta do wysokości paska i prawej strony

Czyli podsumowując, rysowanie ciągiem od lewej do prawej strony. Musiałem zmienić ten plan. Nie zadziałała tutaj jedna rzecz, mianowicie rysując ścieżkę z osi y i skręcając w prawo odnosząc się do lewego rogu kalendarza otrzymałem inny kąt niż rysując z osi x i skręcając również w prawo, tym razem odnosząc się do prawego rogu. Gołym okiem widać było, że rogi są zupełnie różne i nie tędy droga.

Podejście drugie

Trzeba było zmienić założenia, skoro te rogi rysują się inaczej, to narysuję róg po lewej, następnie użyję metody moveTo, aby przeskoczyć na prawą stronę i narysuję róg po prawej, to zadziałało tak jak chciałem:

Co jest trudnego w narysowaniu dwóch zaokrąglonych rogów?

Więc dorysuję jeszcze tą pionową linię, znowu użyję metody moveTo, zamknę ścieżkę i skończone, prawda?

Co jest trudnego w narysowaniu dwóch zaokrąglonych rogów?

Ghrrr…

Podejście trzecie

Noo dobra, ale kąty mi się zgadzają, a to najważniejsze, więc może utworzę drugą metodę, która dorysuje mi ten brakujący trójkąt? Powinno zadziałać prawda? Geometrycznie działa, niestety wizualnie powstały artefakty (pewnie z powodu pikseli, na które musiała być przeliczona linia prosta) (poniżej powiększona grafika)

Co jest trudnego w narysowaniu dwóch zaokrąglonych rogów?

 

Podejście czwarte

Całkiem to jest zabawne prawda? Niby tylko narysowanie pół okręgu, ale ile przygód może człowieka spotkać? Noo dobra, kolejna zmiana podejścia, jak wiadomo piksele to kwadraty ułożone jeden przy drugim, w związku z tym narysujmy lewą część, a osobną ścieżką prawą część, tylko niech mają pionowe linie styku. Tym razem się wreszcie się udało. Poniżej działający kodzik.

    override fun onSizeChanged(xNew: Int, yNew: Int, xOld: Int, yOld: Int) {
        super.onSizeChanged(xNew, yNew, xOld, yOld)
        endX = xNew.toFloat()
        endY = yNew.toFloat()
        startX = paddingLeft.toFloat()
        startY = paddingTop.toFloat()
        endX -= paddingRight
        endY -= paddingTop
        barY = (endY - startY) / 6
        angleY = barY / 2
        angleX1 = startX + angleY
        angleX2 = endX - angleY
    }

    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec)
    }

    private fun makeBarLeftAnglePath(): Path {
        val pathLeftAngle = Path()
        pathLeftAngle.moveTo(startX, barY)
        pathLeftAngle.lineTo(startX, angleY)
        pathLeftAngle.cubicTo(startX, angleY, startX, startY, angleX1, startY)
        pathLeftAngle.lineTo(angleX2+1, startY)
        pathLeftAngle.lineTo(angleX2+1, barY)
        pathLeftAngle.close()
        return pathLeftAngle
    }

    private fun makeBarRightAnglePath(): Path {
        val pathRightAngle = Path()
        pathRightAngle.moveTo(endX + 1, barY)
        pathRightAngle.lineTo(endX + 1, angleY)
        pathRightAngle.cubicTo(endX + 1, angleY, endX + 1, startY, angleX2, startY)
        pathRightAngle.lineTo(angleX2, barY)
        pathRightAngle.close()
        return pathRightAngle
    }

Jak widać pozmieniałem też trochę nazwy zmiennych.

  • barY to wysokość paska na nosi Y
  • angleY to miejsce gdzie zaczynamy rysować kąt
  • axngleX1 to miejsce gdzie kończymy rysować kąt po lewej
  • angleX2 to miejsce gdzie kończymy rysować kąt po prawej

Podsumowując, nie wiedziałem, że po tylu latach problem sprawi mi narysowanie dwóch półokręgów w Androidzie. To była całkiem ciekawa przygoda, trochę nauczyła mnie pokory. Dlatego też ten wpis pojawia się w sobotę, ponieważ te wszystkie przygody zajęły mi o wiele więcej czasu niż myślałem. Ale cóż, tak to właśnie jest z przygodami, w następnej części zajmiemy się wyświetlaniem aktualnej daty na kalendarzu. Kod w aktualnej wersji jest dostępny na githubie.

Ten mityczny sukces – DSP#15

A czy Ty odniosłeś już sukces? Jeśli nie, to dlaczego? Czym jest ten mityczny, wymarzony, upragniony przez wszystkich sukces? A może w ogóle jest nieosiągalny i ci wszyscy mówcy motywacyjni tylko udają, że im się udało? Wpis jest nie jako rozwinięciem wpisu o piekle motywacji sprzed kilku tygodni, polecam się z nim zapoznać, żeby wyrobić sobie kontekst dla tego co będzie poniżej.

tl;dr -> patrz podsumowanie

Jak zmierzyć sukces?

To jest bardzo ważne pytanie na początek. W jaki sposób zmierzyć sukces? Zastanawiałeś się kiedyś co jest dla Ciebie sukcesem? Czy jest to może 15k na koncie co miesiąc, czy coś innego? Dochodzimy tutaj do bardzo ważnej rzeczy, mianowicie nie da się zrobić czegoś, co jest.. niezdefiniowane? Kilkukrotnie podchodziłem do kanału Miroburn i jego subskrypcji YouTube, materiały wrzucane codziennie muszą być dobre, jeśli decyduję się poświęcić na nie czas. I faktycznie, idzie mu to coraz lepiej i mnie przekonał, ja subskrybuję. Ale Mirek ma jeszcze jedną fajną rzecz, jest to strona trzypoziomy.pl. Już wiesz do czego zmierzam? Każdy sukces musi być konkretnie oznaczony, najlepiej z deadlinem. Dlatego też sukcesem nie jest zarabiać dużo pieniędzy. Sukcesem jest zarabiać 15k co miesiąc przez rok, jeśli w ten właśnie sposób definiujesz swoje „dużo pieniędzy”. A sukces zawodowy? Kiedy go osiągamy? Który awans jest tym ostatecznym sukcesem zawodowym? Czy to nie jest tak, że każdy kolejny awans, no ok, ale to tylko krok.. A latka lecą. Ale oto już za rogiem, już niedaleko czeka na nas ten mityczny sukces?

Ten mityczny sukces

Wystarczy zamienić koniec projektu na sukces i wszystko się zgadza.

Czy potrafisz wymienić swoje sukcesy?

Akapit powyżej to opis metody zarządzania znanej jako smart, tylko zamieniłem cel na sukces. Bieżący akapit jest ważny. Co z sukcesami które osiągnąłeś? Jesteś w stanie wymienić z głowy, albo może masz spisane.. jakiekolwiek? Noo właśnie.. To trochę przykro, że coś udaje się osiągnąć, wdrapuje się na jakąś górkę i zamiast wbić swój sztandar.. Zasuwa się dalej. Jeśli chodzi o mnie, to jeśli przewiniesz do góry, jest timeline (który muszę zaktualizować w ogóle). Albo nie, nie przewijaj, masz linka 😉 Mam tam rozpisane na linii czasu rzeczy które udało mi się osiągnąć. Jest to tylko półśrodek, myślę o czymś lepszym, w stylu moja własna „Hall of fame”, (żebym nie tylko zostawiał sztandar na każdej górce, ale budował własny fort), gdzie wszystko będzie opisane dokładnie. Ale to jest na liście pomysłów póki co. A jak wygląda to u Ciebie?

Ten mityczny sukces

Zdobyte certyfikaty to również Twoje sukcesy. Więc dlaczego nie wiszą na ścianie, żebyś codziennie mógł być z siebie dumny? Niestety powyższe zdjęcie, to rzadko spotykany widok.

Sukces maleje z czasem?

Patrząc wstecz i z całych sił starając się sobie przypomnieć co udało się osiągnąć zaczynamy dostrzegać małe, nieistotne coś, co wtedy nazwalibyśmy sukcesem. Ale dziś już nie. W temacie certyfikatów, pewnie każdy ma dyplom pasowania na ucznia z podstawówki. Śmieszne, prawda, nic nie znaczące? Ale gdybyśmy w podstawówce nie nauczyli się dodawać i mnożyć, to nigdy nie ukończyli byśmy studiów zdobywając tytuł inżyniera, prawda? Sukces się nie dezaktualizuje, po prostu z czasem wydaje nam się mały i nic nie znaczący. Ale to nic, wtedy liczenie i czytanie kosztowało nas sporo wysiłku, tak jak w tym momencie sporo wysiłku kosztuje nas zdobywanie kolejnych umiejętności. Warto zdawać sobie z tego sprawę, że za każdym sukcesem stoi jakiś wysiłek. I to właśnie on tak naprawdę jest dokumentowany przez sukces, a nie to, co tak naprawdę osiągnęliśmy.

Ten mityczny sukces

Żeby nie było 😉

Małe kroki

Jakiś czas temu Gutek zaczął wrzucać na swój profil facebookowy trzy rzeczy, które udały mu się danego dnia. To jest naprawdę świetny pomysł. Każdego wieczoru napisz co Ci się udało, w ciągu roku uzbiera się ponad tysiąc sukcesów, to więcej niż niejeden mówca motywacyjny! 😉 A tak serio, metoda małych kroczków jest dobra. Za każdym razem kiedy osiągniesz, cokolwiek choćbyś uczył się html i stworzył stronę z samouczka, to jest Twój sukces. Szanuj go i na nim buduj kolejne.

Ten mityczny sukces

Stockowa fotografia małych kroków, musi być, żeby wpis wyglądał pro.

Podsumowanie

Podsumowując, wszystko może być Twoim sukcesem, ale ważne jest, żeby być tego świadomym. Sukces jest wtedy kiedy coś sobie założysz, określisz ramy czasowe i zadaniowe, oraz doprowadzisz tą sprawę do końca tak jak chciałeś. A potem sobie to zapisz, najlepiej w widocznym miejscu i patrz na efekty swojej pracy często, a najlepiej codziennie. Eksponuj swój sukces, bo on pokazuje nie tylko to, czego dokonałeś, ale również to, ile pracy Cię to kosztowało.

Postscriptum

Wracając jeszcze do ściany dyplomów, mam jeszcze jedną myśl na ten temat. Z racji tego, że ostatnio urodziła mi się córa często bywam w laboratorium, krew, mocz, te historie. I spośród kilku w okolicy wybrałem to, w którym wszedłem i mogłem poczytać o ich osiągnięciach, bo w poczekalni mają ich całą ścianę. To naprawdę wygląda profesjonalnie, widzę, że starają się dbać o jakość swoich usług i ciągle uczą się czegoś nowego. Dalej było już tylko lepiej, pełen profesjonalizm i z czystym sumieniem mogę polecić SanteLab Wrocław, pomimo, że nikt mi za tą rekomendację nie zapłacił. Ale w mojej opinii, widać pasję, profesjonalizm, zaangażowanie w to co robią. I to widać od samego wejścia.

Nowe wymiary, ścieżki w Androidzie – DSP#14

Dzisiaj skupimy się na tym, jak rysować niestandardowe elementy w klasie View. Tzn, będziemy rysowali standardowe (póki co), ale w niestandardowy sposób, a konkretnie korzystając ze ścieżek. Zapraszam!

Ścieżki w Amdroidzie, czyli obiekty typu Path są tworzone ze zbioru kolejnych punktów. Na szczęście nie musimy ich kolejno wpisywać, możemy też użyć metod typu moveTo (przesunięcie), lineTo (narysowanie linii od miejsca gdzie się znajdujemy do podanych punktów), czy arcTo(narysowanie łuków), close (prowadzi do punktu gdzie zaczęliśmy) oraz podobnych. Dlatego też na początek skupmy się na nadaniu właściwych lokalizacji w naszym kalendarzu. Efekt wygląda tak:

Nowe wymiary, ścieżki w Androidzie - DSP#14

Z kolei kodzik potrzebny do narysowania, w ten sposób:

    override fun onDraw(canvas: Canvas?) {
        super.onDraw(canvas)
        canvas?.drawPath(makeBackgroundPath(), backgroundPaint)
        canvas?.drawPath(makeBorderPath(), borderPaint)
        canvas?.drawPath(makeBarPath(), barPaint)
    }

    override fun onSizeChanged(xNew: Int, yNew: Int, xOld: Int, yOld: Int) {
        super.onSizeChanged(xNew, yNew, xOld, yOld)
        width = xNew.toFloat()
        height = yNew.toFloat()
        startLeft = paddingLeft.toFloat()
        startTop = paddingTop.toFloat()
        width -= paddingRight
        height -= paddingTop
        startBar = height / 6
    }

    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec)
    }

    private fun makeBarPath(): Path {
        val path = Path()
        path.moveTo(startLeft, startBar)
        path.lineTo(startLeft, startTop)
        path.lineTo(width + 1, startTop)
        path.lineTo(width + 1, startBar)
        path.close()
        return path
    }

    private fun makeBorderPath(): Path {
        val path = Path()
        path.moveTo(startLeft, startBar)
        path.lineTo(startLeft, height)
        path.lineTo(width, height)
        path.lineTo(width, startBar)
        return path
    }

    private fun makeBackgroundPath(): Path {
        val path = makeBorderPath()
        path.close()
        return path
    }

Jak widać zaczyna to już powoli przypominać kształtem kalendarz. A co się tu dzieje? W metodzie onDraw() na płótnie zostaje wywołana metoda drawPath(), która oznacza rysowanie ścieżki między punktami. Za te ścieżki odpowiadają metody make, w których po kolei dokłada się do nich element. Jak widać ścieżka dla tła różni od ścieżki do obrysowywania tylko tym, że została zamknięta, dzięki temu tło będzie wypełnione i obramowane z trzech stron, z kolei do góry będzie znajdował się pasek. Dziwić mogą te wartości +1 i mówiąc szczerze, mnie też dziwią. Powstała przy rysowaniu mała niedokładność (być może wynikająca z Paint.STROKE), która sprawiała że obrysowanie wystawało o jeden piksel w prawo. Poprawiłem więc szerokość paska aby się dopasował. Ustawiłem nawet wymuszenie układu ekranu z prawej do lewej, żeby sprawdzić jak to się zachowa, ale wyglądało w porządku. W związku z czym zostawiam to przesunięcie o jeden piksel, ponieważ usuwa ono wizualny artefakt. Z pozostałych zmian od poprzedniej wersji, ustawiłem szerokość obramowania na jeden piksel (takie były założenia od początku, po prostu celem testów ta linia była szeroka), a także poprawiłem trochę obliczanie szerokości i wysokości, poprzednim razem podwójnie ucinał obraz z prawej strony. Doszła też nowa wartość startBar według której obliczane są wysokości dokąd namalować tło, oraz odkąd malować pasek. I to działa, bo jeśli usuniemy sobie metodę odpowiedzialną za rysowanie paska otrzymamy taki efekt:

Nowe wymiary, ścieżki w Androidzie - DSP#14

Zmiany do tego momentu są zaccomitowane jako paths.

To tyle na dzisiaj, dosyć krótko, ale zbliża się koniec konkursu, a do zrobienia zostało już tylko kilka elementów. W ramach rekompensaty zapraszam na kolejny post który pojawi się w ciągu 5 minut 😉 Będzie zdecydowanie dłuższy i ciekawszy. A w następnym poście projektowym obsłużymy odpowiednio zagięcia w rogach kalendarza, oraz dodamy wyświetlanie daty na środku.

Jak wykorzystać sztuczną inteligencję do rysowania, autodraw – DSP#13

Nigdy nie byłem szczególnie uzdolniony plastycznie. Już od czasów podstawówki moje oceny z plastyki jakoś szczególnie nie imponowały. Nie to, że nie chciałem ślicznie rysować, po prostu nie potrafiłem. Przyzwyczaiłem się do tego, świat wokół mnie również się przyzwyczaił. Było nam z tym dobrze, ale oto ostatnio pojawiło się coś, co zupełnie łamie zasady. I podoba mi się!

Autodraw

Z racji tego, że rozmawiamy o rysowaniu, to ja może pokażę, jak narysować samochód w czterech krokach:

Jak wykorzystać sztuczną inteligencję do rysowania, autodraw

Czyli rysujemy jedno koło, następnie drugie koło, karoserię i… wybieramy interesujący nas samochód. Kto narysował by właśnie w ten sposób? Jak widać, da się tutaj odnaleźć pewien schemat.. Narzędzie które tutaj opisuję nazywa się Autodraw i jest dostępne tutaj. Autodraw nie dlatego, że pozwala narysować tylko samochód, ale dlatego, że pozwala narysować bardzo dużo rzeczy w automatyczny sposób.

Jak wykorzystać sztuczną inteligencję do rysowania, autodraw

Całość działa w ten sposób, że na podstawie tego jak użytkownicy rysują daną rzecz program dedukuje co użytkownik miał na myśli i podpowiada gotowe rozwiązanie. Ale skąd program wie, jak użytkownicy rysują np ciastko, sklep, czy fortepian?

Quick, draw!

Tutaj należy wspomnieć o drugim eksperymencie Google, który jest dostępny o wiele dłużej. Nazywa się Quick, draw, podziałać w nim można w tym miejscu. Działa w ten sposób:

Jak wykorzystać sztuczną inteligencję do rysowania, autodraw

Dostajemy polecenie narysowania czegoś, a następnie sztuczna inteligencja musi rozpoznać co narysowaliśmy. Z racji tego, że projekt jest ogólnoświatowy powstaje tam sporo rysunków.

Jak wykorzystać sztuczną inteligencję do rysowania, autodraw

Na podstawie których jest prowadzone wnioskowanie w jaki sposób ludzie rysują konkretne przedmioty. I to podejrzewam stąd pochodzą dane do programu Autodraw.

Więcej

Piszę o tym z kilku powodów, po pierwsze uważam narzędzie Autodraw za bardzo przydatne. To jest bardzo fajny pomysł, żeby mój bohomaz przekształcić w coś ładnie narysowanego. Po drugie, mamy tutaj bardzo ciekawe zastosowanie sztucznej inteligencji, która uczy się jak ludzie rysują i na tej podstawie podpowiada konkrety. Więcej przykładów zabaw z AI można znaleźć np na tej stronie.

Reasumując, mam nadzieję, że z aplikacji Autodraw zacznę korzystać regularnie. Rozwiązuje jeden z moich problemów, czyli całkowity brak umiejętności rysowania. Myślę, że Tobie również się przyda. Dzięki za uwagę!

Jak wykorzystać sztuczną inteligencję do rysowania, autodraw

Rozmiar i położenie w android view – DSP#12

System związany z wymiarami oraz położeniem jest w Androidzie nieco skomplikowany. Z racji tego, że na telefon możemy patrzeć zarówno w pionie jak i poziomie, biblioteki i aplikacje muszą się przebudowywać pod konkretne położenie telefonu. Dodatkowo każde urządzenie posiada inny rozmiar, w związku z tym stosowaniem pikseli mija się z celem. Jednostki te zostały zastąpione przez dp dla grafiki, oraz sp dla tekstów.

Rozmiar w klasie View

Aby poznać rozmiar obszaru który możemy wykorzystać korzysta się z metody onSizeChanged(). Jest ona wywoływana za każdym razem kiedy widok jest tworzony, oraz kiedy jest przebudowywany, np z powodu zmiany orientacji. Przyjmuje 4 parametry: xNew, yNew, xOld, yOld. Nietrudno się domyślić co one tak właściwie znaczą, ale dla porządku, te z przedrostkiem new oznaczają aktualny, nowy rozmiar widoku. Z kolei te z dopiskiem old stare rozmiary.

Margin a padding

Różnice pomiędzy tymi dwoma są takie same jak w CSS, tzn margin oznacza po prostu odsunięcie się od danego obiektu. Z kolei padding odstęp zawartości od granicy naszego obiektu. W swojej implementacji traktuję je tak samo, tzn padding będzie również odsuwał ramkę, oraz tło.

Implementacja

Dzisiejsza implementacja prezentuje się następująco:

    private var height = 0.0F
    private var width = 0.0F
    private var startLeft = 0.0F
    private var startTop = 0.0F

.....

    override fun onDraw(canvas: Canvas?) {
        super.onDraw(canvas)
        canvas?.drawRect(startLeft, startTop, width, height, backgroundPaint)
        canvas?.drawRect(startLeft, startTop, width, height, borderPaint)
        canvas?.drawRect(80F, 80F, 350F, 350F, barPaint)
    }

    override fun onSizeChanged(xNew: Int, yNew: Int, xOld: Int, yOld: Int) {
        super.onSizeChanged(xNew, yNew, xOld, yOld)
        width = xNew.toFloat()
        height = yNew.toFloat()
        startLeft = paddingLeft.toFloat()
        startTop = paddingTop.toFloat()
        val xpad = (paddingLeft + paddingRight).toFloat()
        val ypad = (paddingTop + paddingBottom).toFloat()
        width -= xpad
        height -= ypad
    }

Tworzę sobie cztery zmienne zawierające odpowiednio wysokość, szerokość, punkt startowy w poziomie, oraz w pionie. Następnie w metodzie onSizeChanged() przypisuję te wartośći, ustawiając je odpowiednio w zależności od wartości paddingów. Jak widać w metodzie onDraw() rysujemy już korzystając z nowych wartości. A tak prezentują się efekty naszej pracy w pionie:

A tak w poziomie:

Wszystko to zostało obsłużone przez taki kodzik znajdujący się w demie:

.....
    android:layout_marginLeft="10dp"
    android:layout_marginRight="10dp"
    android:layout_marginTop="10dp"
    android:layout_marginBottom="10dp"
    tools:showIn="@layout/activity_main">

    <pl.digitalzombielab.dayview.DayView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:barColor="@color/colorAccent"
        android:paddingLeft="50dp"
        android:paddingRight="100dp"
        android:paddingTop="10dp"
        android:paddingBottom="35dp"
        android:id="@+id/dayView"/>

Przeliczanie dp i px

Na koniec chciałem jeszcze wrócić na chwilę do początku i jednostek dp oraz px. Istnieje ogólny wzór przeliczania tego, korzystając z tej tabelki:

  • 0.75 on ldpi (120 dpi)
  • 1.0 on mdpi (160 dpi; baseline)
  • 1.5 on hdpi (240 dpi)
  • 2.0 on xhdpi (320 dpi)
  • 3.0 on xxhdpi (480 dpi)
  • 4.0 on xxxhdpi (640 dpi)

A następnie w zależności od tego, czy potrzebujemy pikseli, czy dp:

  • px = dp * (dpi / 160)
  • dp = px / (dpi / 160)

Na szczęście nie musimy tego robić 🙂 zajmują się tym maszyny, ale dobrze mieć świadomość, jaki rozmiar powinien mieć obrazek, aby dobrze wyświetlał się na konkretnym ekranie. Tego typu informacje znajdują się też np na tej stronie.

To tyle jeśli chodzi o rozmiary, w przyszłości pewnie będziemy co nieco więcej przeliczać, chcąc np aby aby pasek do góry kalendarza miał prawidłowy rozmiar, albo, żeby tło wyświetlało się do odpowiedniego miejsca. Wszystkie zmiany znajdują się na githubie oznaczone komentarzem 'size’.

All you need (for game development) is LÖVE – DSP#11

W ramach ostatnich zajęć z tworzenia stron www które prowadzę z dzieciakami jako WowSchool, postanowiłem odejść trochę od standardowego planu zadań i pokazać im trochę programowania gier. A skłonił mnie do tego ten wpis, za który serdecznie dziękuję Oli Banasiak, z którym też szczerze polecam się zapoznać, żeby dowiedzieć się nieco więcej na temat samego silnika, jakim jest LÖVE. A ja dzisiaj skupię na czymś nieco innym, mianowicie pokażę jaki kodzik udało się wygenerować z dzieciakami w ciągu godziny, a także kilka obserwacji na temat programowania.

Pierwsza ważna informacja, w LÖVE piszemy korzystając z Lua. Nigdy wcześniej nie zetknąłem się z tym językiem programowania, nawet z nazwy, tym bardziej byłem ciekawy co to takiego. I cóż, w temacie tego co było mi potrzebne, poczułem się jak w domu. Tzn, jeśli zupełnie nie zmieniamy paradygmatu, to wszystkie języki (które znam) oferują coś takiego jak funkcje, czy tam metody. Instrukcje warunkowe, oraz pętle. I we wszystkich z którymi się zetknąłem zostało to zrealizowane ifami, forami, etc.. Różnice dotyczą najczęściej klamerek, średników i tego typu historii. Oraz głębiej na pewno różnic jest o wiele więcej, ale chodzi mi o to, że pisanie podstawowych komend nie różni się tak bardzo. Zresztą, spójrzcie na kodzik poniżej:

function love.load()
    r1 = {
        x = 10,
        y = 100,
        width = 100,
        height = 100
    }

    r2 = {
        x = 250,
        y = 120,
        width = 150,
        height = 120
    }
end

function love.draw()
    local mode
    if checkCollision(r1, r2) then
        mode = "fill"
    else
        mode = "line"
    end
    --love.graphics.print("O jaka fajna gra! Powinna dostać oskara!", 0, 0)
    --love.graphics.circle(mode, x, y, 50)
    love.graphics.rectangle(mode, r1.x, r1.y, r1.width, r1.height)
    love.graphics.rectangle(mode, r2.x, r2.y, r2.width, r2.height)
end

function love.update(dt)
    if love.keyboard.isDown("right") then
        r1.x = r1.x + 10
    end
    if love.keyboard.isDown("left") then
        r1.x = r1.x - 10
    end
    if love.keyboard.isDown("up") then
        r1.y = r1.y - 10
    end
    if love.keyboard.isDown("down") then
        r1.y = r1.y + 10
    end

end

function checkCollision(a, b)
    local a_left = a.x
    local a_right = a.x + a.width
    local a_top = a.y
    local a_bottom = a.y + a.height

    local b_left = b.x
    local b_right = b.x + b.width
    local b_top = b.y
    local b_bottom = b.y + b.height

    if a_right > b_left and
        a_left < b_right and
        a_bottom > b_top and
        a_top < b_bottom then
        return true
    else
        return false
    end
end

Czytając ją, nawet widząc ten język po raz pierwszy, wszystko wydaje się jasne, prawda?

A tak działa kodzik znajdujący się powyżej:

Podsumowując, chciałem bardzo polecić takie wycieczki krajoznawcze po za własny zakres zainteresowań. To działa naprawdę odświeżającą, nowy język, nowe problemy, a do tego jakaś mała, szybko dająca efekty biblioteczka i czysta zabawa. Wiedzieliście np, że Lua umożliwia zwracanie większej liczby wartości, niż tylko jedna? Ciekawe, jak by wpłynęło to na programowanie, gdyby takie możliwości były dostępne w językach takich jak java, czy C#.

Metoda onDraw w Androidzie – DSP#10

Aplikacje służące do rysowania palcem po ekranie to jeden z pierwszych, które piszemy przechodząc samouczki (przynajmniej te, na platformy mobilne). Co ciekawe, w Androidze zarówno tworzenie widoków jak i rysowanie palcem jest obsługiwane przez ten sam zestaw klas. W przypadku rysowania mamy płótno, obiekt pędzla i metodę typu onTouch(). W przypadku tworzenia widoków metody narysuj kwadrat, narysuje linię, etc..

Wspólną dla obu tych podejść jest metoda onDraw(), która jak sama nazwa wskazuje służy do rysowania. A tak wyglądają efekty moich ostatnich prac:

Istna sztuka współczesna, jak by co można składać oferty 😉 A teraz co się tutaj właściwie dzieje? Tło tego widoku pochodzi z klasy MainActivity, w której wszystko testuję:

dayView.cardBackgroundColor = Color.YELLOW

Z kolei ten różowy kolor pochodzi z pliku xml definiującego layout, również w testowej aplikacji:

app:barColor="@color/colorAccent"

Nie przetestowałem jeszcze wszystkich atrybutów, na to będzie czas później, ale póki co z umiarkowanym optymizmem można założyć, że będzie dobrze.

A teraz przejdźmy do mięska, wklejam kod odpowiedzialny za narysowanie tych fragmentów:

    private var barPaint = Paint()
    private var textDayPaint = Paint()
    private var textMonthPaint = Paint()
    private var backgroundPaint = Paint()
    private var borderPaint = Paint()
    private var day = String()
    private var month = String()

    var barColor = -12627531
        set(value) {
            field = value
            barPaint.color = value
            invalidate()
        }
    var textColor = -16777216
        set(value) {
            field = value
            textDayPaint.color = value
            textMonthPaint.color = value
            invalidate()
        }
    var cardBackgroundColor = -1
        set(value) {
            field = value
            backgroundPaint.color = value
            invalidate()
        }
    var borderColor = -2302756
        set(value) {
            field = value
            borderPaint.color = value
            invalidate()
        }
    var date = Date()
        set(value) {
            field = value
            day = date.day.toString()
            month = date.month.toString()
            invalidate()
        }

    private val rect = Rect()

    constructor(ctx: Context) : super(ctx) {
        init()
    }

    constructor(ctx: Context, attrs: AttributeSet) : super(ctx, attrs) {
        val a = context.theme.obtainStyledAttributes(
                attrs,
                R.styleable.DayView,
                0, 0)
        val typedValue = TypedValue()
        val theme = context.theme
        try {
            if (theme.resolveAttribute(R.attr.colorPrimary, typedValue, true))
                barColor = typedValue.data
            barColor = a.getColor(R.styleable.DayView_barColor, barColor)
            textColor = a.getColor(R.styleable.DayView_textColor, textColor)
            cardBackgroundColor = a.getColor(R.styleable.DayView_cardBackgroundColor, cardBackgroundColor)
            borderColor = a.getColor(R.styleable.DayView_borderColor, borderColor)
        } finally {
            a.recycle()
        }
        init()
    }

    private fun init() {
        backgroundPaint.color = cardBackgroundColor
        borderPaint.color = borderColor
        barPaint.color = barColor
        textDayPaint.color = textColor
        textMonthPaint.color = textColor
        backgroundPaint.style = Paint.Style.FILL
        borderPaint.style = Paint.Style.STROKE
        borderPaint.strokeWidth = 10F
        barPaint.style = Paint.Style.FILL

    }

Do góry zostały zdefiniowane nowe obiekty typu Paint, które będą odpowiadały za właściwości graficzne wszystkich obiektów tego widoku, poniżej znajdują się kolory, które już pokazywałem, ale tym razem zostały rozbudowane o settery. Tzn settery dla tych obiektów istniały cały czas, ale w momencie dodania poniżej zmiennej dyrektywy set, można napisać zmodyfikowany setter. W moim znajduje się dyrektywa field = value która oznacza przypisz wartość do pola. Nie musimy podawać nazwy pola, mam zdefiniowane w Kotlinie słówko field. Następnie definiujemy kolor obiektowi typu Paint. A także wywołujemy invalidate() które jest odpowiedzialne za ponowne narysowanie obiektów. Tutaj uwaga, którą będę musiał dodać do readme projektu, w przypadku gdyby te operacje odbywały się po za wątkiem UI (głównym w Androidzie) należy wywołać metodę postInvalidate(). Kolejnym nowym elementem jest metoda init() wywoływana w konstruktorze. Użycie jej jest zalecane, ponieważ metoda onDraw() może być wywoływana bardzo często i dlatego tworzenie w niej za każdym razem obiektów typu Paint mogłoby negatywnie wpłynąć na wydajność. W ciele metody init znajduje się przypisywanie parametrów obiektom Paint, ta metoda na pewno zostanie jeszcze rozbudowana.

Przejdźmy do samego rysowania, odpowiada za to ten kawałek kodu:

    @SuppressLint("NewApi")
    override fun onDraw(canvas: Canvas?) {
        super.onDraw(canvas)
        canvas?.drawPaint(backgroundPaint)
        canvas?.drawRect(50F, 50F, 300F, 300F, borderPaint)
        canvas?.drawRect(80F, 80F, 350F, 350F, barPaint)
        canvas?.drawRoundRect(400F, 400F, 800F, 1000F, 50F, 50F, barPaint)
    }

    override fun onSizeChanged(xNew: Int, yNew: Int, xOld: Int, yOld: Int) {
        super.onSizeChanged(xNew, yNew, xOld, yOld)
    }

    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec)
    }

Metoda drawPaint() odpowiada po prostu za rysowanie zgodnie z wytycznymi znajdującymi się w obiekcie Paint. Dzięki temu jest idealna do np, wypełnienia tła. Z kolei drawRect() rysuje prostokąty, natomiast drawRectF() prostokąty z zaokrąglonymi rogami. Są 2 powody, dla których nie mogę użyć tej metody:

  • Adnotacja „NewApi” oznacza tyle, że ta metoda nie jest kompatybilna ze wszystkimi wersjami androida dla których udostępnię bibliotekę (z dokumentacji wynika, że ta metoda pojawiła się w API 21)
  • Ta metoda rysuje zaokrąglenia do góry i na dole, a ja potrzebował bym tylko do góry.

Tym problemem zajmę się w następnych tygodniach, jak i tym, żeby prawidłowo obsługiwać rozmiary i ustawienia elementów, do czego służą metody onSizeChanged() oraz onMeasure().

To tyle, jeśli chodzi o podstawy rysowania w Androidzie. Całość została zaccomitowana jako „drawing” i wszystkie bieżące zmiany można podejrzeć na githubie pod tym linkiem.