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.

Piekło motywacji – DSP#09

Każdemu czasem się nie chce. No nie wierzę, że są ludzie, którzy zawsze realizują plan z dnia poprzedniego w stosunku 1:1. To prawda, staramy się, żeby tak było, ale 100% skuteczności przez cały czas jest nieosiągalne. Właśnie rozpłakała mi się córka, muszę ją uspokoić i nie mam na to wpływu, tak jak na wiele innych rzeczy w swoim życiu. Potrwa to pewnie pół godzinki, co już generuje mi spóźnienie, bo przecież tego w planie na dzisiaj nie było. A życie jest nieprzewidywalne.

Słuchając mówców motywacyjnych można odnieść wrażenie, że Ci to są ale poukładani. 120% wydajności, przez 100% czasu, realizacja wszystkich celów w życiu i rosnące bogactwo. Pasmo naprzemiennych planów i sukcesów, plan, sukces, plan, sukces, no masakra, każdy by tak chciał. Ale przecież, możesz to osiągnąć, tylko zapisz się na tygodniowe szkolenie, ale zdecyduj się szybko, bo cena promocyjna nie będzie trwała wiecznie, tylko przez pół godzinki po tym darmowym webinarze. I pamiętaj, pracuj ciężko, bo bez tego sukces nie przyjdzie.

Piekło motywacji

Zapier***ać trzeba – ciekawe czy on również dorabia sobie jako mówca motywacyjny?

Noo to zaczynasz ciężką pracę, bo przecież, „za rok będziesz żałował, że nie zacząłeś dzisiaj”. I tworzysz sobie stos zadań, bo przecież dasz radę, ten wewnętrzny ogień, zwłaszcza, kiedy to projekt IT, który przyniesie miliony. No i dajesz radę, więc napędzony sukcesem jedziesz dalej.. Albo i nie dajesz rady już pierwszego dnia.. Po jakimś czasie i tak do tego dojdzie, poprzekładane zadanka ułożą się w stos nie do zdobycia, ale Ty będziesz z nim walczył. Będzie ciężko zasnąć, bo śrubka przykręcona, ale zadania nie zrobione, więc jak tu spać. W końcu, z jednej strony chęć i zobowiązanie w postaci listy zadań, z drugiej absolutna niechęć do robienia czegokolwiek. I taki stan pomiędzy tymi dwoma trwa, aż całkowicie nie wyczerpie Ci się energia.

Piekło motywacji

„Odpocznij. Pole, które odpoczęło daje obfite plony. Ovidius.” Chyba mógłbym zawodowo robić takie obrazki.

Noo i stało się, odpuściłeś. Trackery zadań wyłączone, żadnych planów, robisz minimum i tyle, nic ponad to co wymagane do przeżycia. Czyli powrót do domu, czipsiki, tv, po jakimś czasie zaczynasz o sobie myśleć, że jesteś jak ten pan poniżej. I przypadkiem trafiasz na wykład motywacyjny. Wracasz do punktu pierwszego, a kasa się kręci. Tylko, że przecież nie Tobie, prawda?

Piekło motywacji

Zaczynasz tak o sobie myśleć i wracasz do punktu pierwszego. Poszukiwania motywacji.

Złoty środek?

Ciężka praca jest potrzebna aby odnieść sukces. Ale po ciężkiej pracy należy się zregenerować, nie można cały czas jechać na najwyższych obrotach. A najważniejsze, jeżeli nie będziemy pracowali rozsądnie, to nasza praca i tak pójdzie na marne. Najważniejszym elementem jest właśnie to, żeby pracować mądrze. I do tego odpoczywać regularnie. Z autopsji powiem, że miałem tak jak opisałem powyżej i nie wydaje mi się, żebym był w tym jedyny, prawda? Na szczęście z czasem przyszła refleksja i od jakiegoś roku jestem na w miarę stałym poziomie pomiędzy pracą a relaksem. Planuję, ale staram się na tyle, żeby zrealizować to co chcę, a jeśli się nie uda, to przesuwam. Starałem się i to jest sukcesem. I taki stan polecam każdemu.

Wniosek który tutaj zawarłem nie jest jakoś szczególnie odkrywczy, ale w dobie postprawdy tak trudny do wyłuskania. Z jednej strony mamy w mediach obraz tłuściocha leżącego na kanapie i oglądającego tv, a z drugiej, pięknych, wysportowanych, bogatych i odnoszących sukcesy ludzi. Brakuje jednego, obrazu osoby która zarabia tyle, że uznaje to za wystarczające na spokojne życie. Udaje jej się odnosić swoje własne sukcesy i z tego się cieszy. Nie jest szczególnie piękna, ani szczególnie wysportowana. Ale mając odpowiednio dużo czasu na pracę i siebie jest po prostu szczęśliwa. Każdego dnia, a nie tylko wtedy, kiedy uda jej się osiągnąć ten wymarzony, nienamacalny sukces. Bo sukcesem można nazwać każdy dzień, którego nie będzie się żałowało.

Projekt API, obsługa kolorów biblioteki – DSP#08

Kolejnym krokiem na ścieżce rozwoju biblioteki będzie zaprojektowanie malutkiego API, oraz dodanie obsługi kolorów do poszczególnych elementów, które pojawią się w widoku kartki z kalendarza.

Projektowanie zaczniemy od atrybutów, które dodamy w pliku xml. Będą one miały także swoją reprezentację w javie, przez klasyczne gettery i settery. Atrybutami tymi będą kolory, a mianowicie:

  • barColor
  • textColor
  • cardBackgroundColor
  • borderColor

Tak naprawdę nie wyobrażam sobie, żeby zmieniać któryś z tych kolorów po za kolorem paska, ale API wystawię, biblioteka będzie malutka, ale przynajmniej z szerokimi możliwościami konfiguracji. Aby zdefiniować parametry xmlowe utworzyłem plik attrs.xml w folderze values biblioteki. W pliku tym zdefiniowałem styl o nazwie takiej samej jak aplikacja (to nie jest obligatoryjne), oraz parametry xml, z których będzie można skorzystać. Kodzik

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="DayView">
        <attr name="barColor" format="color" />
        <attr name="textColor" format="color" />
        <attr name="cardBackgroundColor" format="color" />
        <attr name="borderColor" format="color" />
    </declare-styleable>
</resources>

Po zbudowaniu aplikacji możemy już dodać te parametry do dema, ale nic z tego nie wyniknie. Dlatego najpierw obsłużymy je w klasie biblioteki:

    var barColor = -12627531
    var textColor = -16777216
    var cardBackgroundColor = -1
    var borderColor = -2302756
    var date = Date()

    constructor(ctx: Context) : super(ctx)

Więc tak, co tu się dzieje? Do góry mamy deklarację zmiennych (słówko kluczowe var), brak modyfikatora dostępu oznacza w Kotlinie public. W javie chcielibyśmy mieć takie jaki pola prywatne i modyfikatory dostępu do tego. W Kotlinie pola prywatne znaczy dostępne tylko w klasie z której korzystają, a pola publiczne mają już gettery i settery tworzone domyślnie. Przy czym, podobnie jak w C# możemy sobie zmodyfikować metody gettera i settera. Czyli, sama definicja zmiennych wystawia nam całe potrzebne API. Którego użycie w Kotlinie będzie wyglądało tak

dayView.date = Date()

Z kolei w javie automagicznie pojawi nam się odpowiedni setter

    private void test() {
        DayView dayView = new DayView(null, null);
        dayView.setDate(new Date());
    }

Prawda, że fajne?

Wrócę jeszcze do tego, co dzieje się w konstruktorze tego widoku:

    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()
        }

Zmienna a zawiera parametry, które udało się pobrać z xmla. W instrukcji try najpierw sprawdzamy, czy została zdefiniowana zmienna o nazwie colorPrimary. Jeśli tak, to barColor zamiast cyferek które tam ustawiłem przyjmuję tą wartość. Dalej następuje próba odczytania barColoru z konfiguracji widoku w pliku xml. Jeśli to się nie uda, zostaje domyślny kolor (drugi parametr). Podobnie z pozostałymi kolorami.

Reasumując udało się stworzyć konfigurację, której można użyć w pliku xml, wczytać ją do kodu, oraz wystawić API. Całkiem nieźle, jak na te kilka linii kodu. Wszystko zaccomitowane jako „collors and api”. Na następny raz czeka już prawdopodobnie namalowanie tej biblioteki.

Ile warty jest pomysł? tl;dr – absolutnie nic – DSP#07

Zacznijmy od tego, że pomysł jest ważny. Bez niego, nie wykonamy żadnych kolejnych kroków. Ale czy sam pomysł sprawia, że budzimy się milionerami? Ile warty jest pomysł w takim razie?

[su_row]

[su_column size=”1/2″]

Kto spotkał się z tego typu, lub podobną dyskusją w internecie? Zdarzają się nadzwyczaj często, zwłaszcza na grupach programistycznych. Różnie to się kończy, kiedy ktoś nie chce podać żadnych szczegółów, najczęściej po prostu nie otrzymuje pomocy. No bo pomimo szczerych chęci jak takowej udzielić? Kiedy nie wiadomo, na jakie pytanie tak naprawdę, należy odpowiedzieć?

[/su_column]

[su_column size=”1/2″]

Cześć! Powiedzcie mi proszę, jak mogę zaimplementować obsługę obrazków w Androidzie?

Ale o jaką konkretną obsługę Ci chodzi, co chcesz zrobić?

Mam pomysł na aplikację, ale nie mogę powiedzieć..

[/su_column]

[/su_row]

Zdefiniuj pomysł

Żeby móc zacząć rozmawiać, zdefiniujmy dobry pomysł. Dla mnie to taki, za który ludzie będą w stanie zapłacić. Zgadzacie się? Kiedy napisałem te słowa pojawia mi się już pierwszy zgrzyt, tak naprawdę nie płacimy za pomysł, tylko za wykonanie. Nawet najwspanialszy pomysł może być fatalnie wykonany i nigdy nie zadziałać, a nawet najbardziej trywialna i oklepana rzecz może – wykonana świetnie – zyskiwać popularność.

Ile warty jest pomysł

 

Całkiem nowy pomysł

Rzecz o którą dzisiaj naprawdę trudno. Już w 1899 r. Charles H. Duell szef biura patentowego w USA powiedział: „Wszystko, co było do wynalezienia, zostało już wynalezione”. Gdyby dzisiaj ktoś popularny wypowiedział te słowa, pewnie za 100 lat ktoś również uśmiechał by się je czytając. Nie mniej o całkiem świeże pomysły jest bardzo trudno. A dodatkowo, te całkiem świeże pomysły mają spory problem, żeby się przebić. Mówię tutaj o zupełnie nowych pomysłach, produktach, a nie „innowacjach”, którymi to każda firma tak lubi się chwalić. Ludzie wpadają na takie pomysły, ale czy ktoś z nas kojarzy, aby ostatnio korzystać z czegoś zupełnie nowego, z czym nie miał do czynienia wcześniej? Wiem, że to dowód anegdotyczny, ale kiedyś słyszałem o 5% zupełnie nowych pomysłów, które wypalają, niestety nie potrafię znaleźć tych danych, gdyby ktoś, to można śmiało zostawić w komentarzu.

Rozszerzenie czyjegoś pomysłu

Rzecz jest o wiele prostsza, kiedy bierzemy się za poprawienie czegoś co już działa. Działa, ale… może działać lepiej. Ktoś kiedyś wymyślił GUI złożone z okien, od tamtej pory trwa rozwój tego pomysłu, doszło drag’n drop i wiele różnych ułatwiających działanie funkcjonalności. Dodatkowo każdy producent ma swoją wizję jak powinien wyglądać system z okienek, dzięki temu stety-niestety nie mamy jednej wersji okienek wszędzie, różnią się rozmieszczeniem przycisków, funkcjami i działaniem. I dzięki temu korzystają z nich różni użytkownicy, ponieważ różnym ludziom pasują różne rzeczy. Bo jesteśmy różni. Ponadto jesteśmy też zmienni, potrzeba nam odmiany, więc zmieniamy technologie. W związku z tym ten sam pomysł może współistnieć obok siebie będąc wykonanym na różne sposoby.

Ile warty jest pomysł

Mam pomysł!

Więc ile warty jest pomysł, warto go chronić?

W związku z powyższym, czy zupełne ukrycie przed światem swojego pomysłu, to naprawdę najlepsze wyjście? Czy nie lepiej podzielić się nim…? Bądź co bądź, im więcej osób usłyszy, tym więcej opinii zbierze, pozytywnych bądź mniej. Dodatkowo, jeśli poprawiamy jakiś pomysł, warto zrobić sobie mały research, dlaczego nie wypaliło poprzednim razem komuś innemu, albo co ja mogę zrobić lepiej. A przechodząc od teorii do praktyki, od jakiegoś czasu chodzi mi po głowie pomysł na aplikację która zbierała by paragony i na tej podstawie można by było uznać gwarancję. Przeszukałem sieć, jest kilka propozycji tego typu, ale wiem co mógłbym zrobić lepiej. Macie może jakiś pomysł co mogłoby się znaleźć w tej aplikacji? 😉

On zdradził swój pomysł!

O nie, i co teraz…? Noo właściwie to nic, bo czy uznajesz ten temat za na tyle fascynujący, żeby zrobić go samemu? Gdyby to był wykop, to pewnie byłoby: #afera #kradno – zabrali mi mój pomysł. I ten gif:

Ile warty jest pomysł

Swoją drogą bardzo dobry. Ale życie to nie wykop 😉 Jest takie jedno miejsce, gdzie znajduje się najwięcej pomysłów.

Ile warty jest pomysł

Reasumując, ile warty jest pomysł? Jest trochę śmieszno – trochę straszno. Życie jest krótkie i wszystkich pomysłów nie uda się zrealizować, dlatego jak pisałem już dawno temu umiejętność oddzielenia zadań do oddelegowania od zadań do wykonania jest najważniejszą umiejętnością w życiu. Dlatego warto dzielić się pomysłami. Może akurat zainspirujemy kogoś, do zrobienia czegoś o czym w życiu by nie pomyślał. A nam i tak nie starczyło by czasu, albo zrobilibyśmy daną rzecz „gorzej”. [su_note note_color=”#349F8C” text_color=”#000000″ radius=”15″]Pomysł jest jak wstanie z łóżka. Nie gwarantuje, że cokolwiek zrobimy, ale bez tego, na pewno nie zrobimy niczego.[/su_note] A Ty na jaki pomysł wpadłeś ostatnim razem? Podziel się w komentarzu 😉

O implementacji widoków w Androidzie – DSP#06

Android posiada bardzo bogatą kolekcję wszelkiego rodzaju elementów interfejsu użytkownika: przyciski, pola tekstowe, checkboxy, przełączniki, to oczywiście te podstawowe, ale po za nimi występują konkretne widoki całego kalendarza, map, albo np web view – czyli wyświetlenie konkretnego kodu HTML. Wszystkie te widoki mają wspólną nadklasę, z której ja również skorzystam tworząc swoją bibliotekę.

Najkrócej wprowadzając w podstawy, w androidzie rozróżniamy aktywności (dziedziczące po klasie activity i pochodnych), w których znajduje się minimum metoda onCreate(..):

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        val toolbar = findViewById(R.id.toolbar) as Toolbar
        setSupportActionBar(toolbar)
    }

To aktywność jest najczęściej uruchamiana po starcie aplikacji, to ona decyduje który widok wyświetlić. Widoki, dziedziczące po klasie View, tutaj musimy po prostu nadpisać konstruktory. To na tym dzisiaj się skupię, ponieważ udało mi się zaimplementować w bibliotece, oraz uruchomić w aplikacji pierwszy, podstawowy widok, który wyświetla aktualny czas w milisekundach.

Widok, to podstawowy „składnik” dla wszystkich elementów interfejsu w Androidzie. Widoki są zbierane przez grupy widoków ViewGroups , które z kolei są bazą dla layout’ów. Ok, łatwiej to wyjaśnić rysunkiem niż słownie.

Tworzony przeze mnie DayView również dziedziczy po klasie View. A żeby cokolwiek się w nim znalazło należy przeciążyć metodę onDraw(..), ale może najpierw o konstruktorach.

    constructor(ctx: Context) : super(ctx)
    constructor(ctx: Context, attrs: AttributeSet) : super(ctx, attrs)

Tak wyglądają w moim kodzie, pierwszy z nich przyjmuje oczywiście kontekst aplikacji, drugi kontekst i parametry które mogą dotyczyć jego wyglądu i mogą być zdefiniowane w pliku layoutu, jest wywoływany kiedy widok jest łączony ze swoją reprezentacją w xmlu (przyda się to później, kiedy będziemy mieli  kolor tekstu, etc podane w pliku z wyglądem naszego okna).

A teraz co nieco o metodzie onDraw(..), po pierwsze przyjmuje ona parametr Canvas który jest „płótnem” na którym możemy rysować korzystając z metod typu drawOval(..), drawRect(..) i podobnych. Do rysowania potrzebujemy jeszcze obiektu typu Paint który przechowuje nam kolory, rozmiary, kształty, dzięki którym możemy rysować. A teraz kodzik z tego co udało się do tej pory zrobić:

override fun onDraw(canvas: Canvas?) {
        super.onDraw(canvas)
        paint.color = Color.WHITE
        paint.style = Paint.Style.FILL
        canvas?.drawPaint(paint)
        drawTextCenter(canvas, paint, System.currentTimeMillis().toString())
    }

    private fun drawTextCenter(canvas: Canvas?, paint: Paint, text: String) {
        canvas?.getClipBounds(rect)
        val cHeight = rect.height()
        val cWidth = rect.width()
        paint.setTextAlign(Paint.Align.LEFT)
        paint.color = Color.BLACK
        paint.textSize = 40f
        paint.getTextBounds(text, 0, text.length, rect)
        val x = cWidth / 2f - rect.width() / 2f - rect.left
        val y = cHeight / 2f + rect.height() / 2f - rect.bottom
        canvas!!.drawText(text, x, y, paint)
    }

Tak naprawdę powyższy kod oznacza rysowanie tekstu na środku płótna, i zostanie pewnie zastąpiony w bardziej logiczny sposób, ale czemu on taki pokręcony? Cóż, android czasem miewa implementacje, w których ot tak wprost nie da się wyśrodkować czegoś na ekranie. Rozwiązanie problemu pochodzi ze stackOverflow gdzie zostało dobrze opisane, ja je tylko przełożyłem na Kotlina.

Pozostałymi rzeczami które zrobiłem w projekcie jest podłączenie biblioteki do aplikacji, ponieważ jeszcze nie została opublikowana, to nie było akurat specjalnie trudne, wymagało jednej linii:

    compile project(path: ':dayview')

W pliku build.gradle w module app. Potem mogłem dodać już mój widok z modułu dayview, wygląda on tak:

<pl.digitalzombielab.dayview.DayView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:id="@+id/dayView"/>

I oczywiście skorzystać z tego identyfikatora w aktywności, tak wygląda aktywność po wywaleniu zbędnych opcji:

package pl.digitalzombielab.dayviewdemo

import android.os.Bundle
import android.support.v7.app.AppCompatActivity
import android.support.v7.widget.Toolbar
import android.view.Menu
import android.view.MenuItem
import pl.digitalzombielab.dayview.DayView

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        val dayView = findViewById(R.id.dayView) as DayView
        val toolbar = findViewById(R.id.toolbar) as Toolbar
        setSupportActionBar(toolbar)
    }

    override fun onCreateOptionsMenu(menu: Menu): Boolean {
        // Inflate the menu; this adds items to the action bar if it is present.
        menuInflater.inflate(R.menu.menu_main, menu)
        return true
    }

    override fun onOptionsItemSelected(item: MenuItem): Boolean {
        // Handle action bar item clicks here. The action bar will
        // automatically handle clicks on the Home/Up button, so long
        // as you specify a parent activity in AndroidManifest.xml.
        val id = item.itemId


        if (id == R.id.action_settings) {
            return true
        }

        return super.onOptionsItemSelected(item)
    }
}

Całość została zaccomitowana jako „first simple view”.

Kolejnym krokiem będzie stworzenie zdecydowanie ładniejszego wyglądu kartki z kalendarza, a także projekt API z którego będziemy docelowo korzystać.

Bycie programistą to dopiero początek – DSP#05

O programistach różnie się mówi. Część materiałów pochodzących z nietechnicznych mediów, mówi o rozpuszczonych, odizolowanych, robiących jakieś turbo zaawansowane rzeczy personach. O tym jak prezentują nas na filmach, o wszelkiego rodzaju hakierach, to lepiej nawet nie wspominać. 😉 Sami o sobie też różnie myślimy, w którymś momencie robiąc po raz któryś to samo, nasza praca nie musi się jakoś szczególnie różnić od pracy na taśmie. Dlatego część z nas chce wyzwań, robić nowe rzeczy. A część nie lubi zmian, zna się na jakichś systemach i tego się trzyma. Rozwój jest potrzebny, ale o tym może napiszę innym razem. Dzisiaj o tym, że klepanie kodu może być dopiero początkiem, do robienia innych, ciekawych rzeczy.

Bycie programistą to dopiero początek

Blog

Zacznijmy może od blogów, blogosfera IT ma się dobrze. Ostatnio bardzo urosła, z powodu oczywiście konkursu DSP. Ludzie próbują swoich sił, części wychodzi to lepiej, innym gorzej, ale jak mawiał klasyk: „nie o to chodzi jak co komu wychodzi”. Ważne są próby, chęć zrobienia czegoś więcej, co wyróżni nas na rynku, a także pokaże na zewnątrz, że może niekoniecznie IT to taka zamknięta grupa z sobie tylko znanymi problemami. Dobrym przykładem tutaj jest wpis „zrzuciłem cyber-kajdany” u Maćka, pokazujący, że wszyscy w dzisiejszych czasach, nie tylko ludzie związani z branżą IT, możemy mieć bardzo podobne problemy.

Prelekcje

Po za tym spora część z nas uczestniczy, część organizuje, a część preleguje, tzn przekazuje swoją wiedzę dalej na wszelakiego rodzaju konferencjach. Tutaj również pokazujemy, że potrafimy się spotkać, zorganizować i powiedzieć coś ciekawego. Przypomina mi się krótka rozmowa z osobą która przyszła do pubu, kiedy akurat trwały prelekcje na temat Androida. Opowiadała, że nie rozumie treści, ale ta prelekcja była fajna, fajnie, że nam zależy, że robimy coś więcej i w ogóle świetnie to wychodzi. Nie wiem, czy innym grupom zawodowym po za IT zdarza się spotykać i wzajemnie od siebie uczyć. Możliwe, że osoby zajmujące się gotowaniem formują się w takie grupy i razem gdzieś coś robią, ale nigdy nie słyszałem np o hydraulikach którzy spotykali by się i omawiali wprowadzane przez siebie rozwiązania (mogę się mylić, może są takie grupy). IT ma w sobie jakąś taką otwartość, gdzie chętnie nawzajem przekazujemy sobie wiedzę, uczymy kolejnych młodych adeptów, nie wiem czy zostało napędzone przez firmy, które potrzebują ekspertów i tego typu spotkania pozwalają im za darmo część osób wyszkolić, które może kiedyś będą u nich pracować, czy wynika to z nas i chęci dzielenia się wiedzą.

Szkolenia

Dzielenie się wiedzą przejawia się także przez to, że organizujemy szkolenia, powstały nawet specjalne szkoły, które pozwalają wprowadzić ludzi w IT, ale tutaj nie o tym. Skupmy się na tym, że zdarzają się programiści uczący, którzy w ten sposób realizują swoją potrzebę przekazywania wiedzy. To również jest jakaś czynność dodatkowa, która wymaga dogłębnego zrozumienia tematu, a także ćwiczy umiejętności interpersonalne, sprzedaży i prezentacji. Po za tym szkolenia nie muszą odnosić się tylko do osób dorosłych, zdarza się szkolić również dzieci, co pozwala podejść do tego inaczej i zamiast konkretnej wiedzy przekazać „bakcyla” do programowania. Pokazać coś ciekawego, zainteresować.

Video

Kolejną rzeczą jest video, ostatnio pojawił się oczywiście Maciek ze swoim vlogiem, który w krótkim czasie dobrze rozwinął kanał, ale nie on jedyny działa na tym polu. Bardzo popularny jest Mateusz – JavaDevMatt który od dłuższego czasu się tym zajmuje, tworząc kilka różnych serii, dodatkowo MiroBurn i jego vlogi, oraz np Karol Wójciszko. Jest też kanał Pasja informatyki, prowadzony przez M. Zelenta, ale ten pan nie jest związany bezpośrednio z branżą IT, pracuje jako nauczyciel, a w ramach kanału przekazuje wiedzę dalej. Ja też podejmowałem nieśmiałe próby działania w tym temacie, na pewno jeszcze wrócę do tej formy. A na YouTube imho brakuje polskich materiałów o IT, które nie byłyby kursami programowania.

Audio

Po za video czasem wykorzystujemy same audio, a mówiąc wprost – nagrywamy podcasty, znam trzy polskie o IT: Devtalk, Dev Review realizowany przez Dariusza Pawlukiewicza z bloga ForeverF[r]ame, a ostatnio pojawił się nowy z Wrocławia: Ostra Piła. Ale jest tutaj jeszcze sporo miejsca, żeby nagrywać coś ciekawego.

Książki

Kolejną aktywnością, która zdarza się w naszej branży jest pisanie książek. Jest ich naprawdę sporo, o różnej tematyce, praktyczne kursy, zarządzanie czasem i pieniędzmi, zdarza się trafiać na krótkie, tematyczne ebooki rozdawane za darmo, w zamian za np. zapisanie się na newsletter.

Poza IT

Po za tym warto pamiętać, że bycie programistą nie oznacza, że musimy mieć tylko hobby związane z IT, kilka przykładów dodatkowych zajęć to np Andrzej Krzywda, który po za pracą jest szachistą odnoszącym konkretne sukcesy, albo Tomasz Zieliński, kiedyś twórca Transportoida, aktualnie lubi polatać sobie na paralotni. Warto czasem oderwać się od tematów typowo IT, poszerzyć swoje horyzonty i zrobić coś innego, co w żaden sposób nie będzie związane z naszą pracą zawodową.

Bycie programistą to dopiero początek

Reasumując, każdy może znaleźć dodatkowe zajęcia, które dają mu sporo frajdy i pozwolą dodatkowo się rozwinąć, warto też próbować różnych aktywności, łączyć je, a także wyruszać na niezbadane jeszcze wody gdzie nikogo wcześniej nie było i przecierać szlaki kolejnym programistom. Z tej przyczyny warto rozwijać pełen wachlarz swoich umiejętności. Bycie programistą nie wyklucza posiadania ciekawego hobby, czy bycia duszą towarzystwa. Pisał o tym ostatnio choćby Gutek w swoim wpisie, o bardzo dobrym tytule: „Nie tylko kodem żyje człowiek”. A może robisz coś ciekawego, czym chciałbyś się pochwalić, albo o tym nie wspomniałem? Komentarze są idealnym miejscem, zapraszam! 😉

Rozpoczęcie implementacji, różnice pomiędzy aplikacją a biblioteką – DSP#04

Czwarty wpis w ramach konkursu a tak naprawdę dopiero zaczynamy tworzenie kodu. Cóż, jak pisałem poprzednio projekt jest malutki i jednym z moich celów jest skończyć go i zrobić jako „prawdziwy open source”. Dlatego nie skupię się na ilości, ale raczej jakości, dokumentacji i promocji. Ale małymi kroczkami, zacznijmy od utworzenia nowego projektu..

Rozpoczęcie implementacji biblioteki nie stanowi wielkiej filozofii: File -> New -> New project. Projekt nazwę DayViewDemo, z kolei pakiet będzie nosił nazwę digitalzombielab, tak jak moje konto w sklepie Play.

Rozpoczęcie implementacji, różnice pomiędzy aplikacją a biblioteką

Kolejnym krokiem jest utworzenie modułu, który będzie naszym docelowym projektem, czyli biblioteką android.

Rozpoczęcie implementacji, różnice pomiędzy aplikacją a biblioteką

Czyli klikamy File -> New -> New module i z listy dostępnych wybieramy Android library. Co nowego czego nie było w aplikacji pojawia się po tych modyfikacjach. Po pierwsze plik settings.gradle zawiera teraz dwa moduły.

include ':app', ':dayview'

Dodatkowo pojawił się nowy plik build.gradle, który jest niemalże identyczny (nie posiada załączonej biblioteki do layoutu, oraz support design) jak ten sam plik z modułu app.

Rozpoczęcie implementacji, różnice pomiędzy aplikacją a biblioteką

Na powyższym zrzucie widać również różnicę w strukturach katalogów, moduł biblioteki nie zawiera np layoutu, ani menu. Plik manifestu w libce również jest krótszy, ponieważ nie zawiera żadnej aktywności, wygląda w ten sposób.

<manifest xmlns:android="http://schemas.android.com/apk/res/android"

    package="pl.digitalzombielab.dayview">

    <application android:allowBackup="true" android:label="@string/app_name"
        android:supportsRtl="true">

    </application>

</manifest>

Zajrzyjmy teraz do kodziku aktywności, a tam.. Java. 53 linie które generują się na początku każdego projektu. Uruchomienie aktywności, obsługa layoutu, menu, etc.. A miał być Kotlin. Z racji tego, że nie lubię bezsensownej roboty nie będę przepisywał auto-wygenerowanego kodu. Skrót ctrl+shift+a daje nam dostęp do czegoś, co nazwano „akcje”, czyli możemy sobie stamtąd uruchomić jakieś funkcje naszego IDE, etc.. Wpisuje plugins, i wyszukuję w otwartym okienku Kotlina.

Rozpoczęcie implementacji, różnice pomiędzy aplikacją a biblioteką

Instaluję wtyczkę, restartuję intelliJ. To dobry moment na zapisanie zmian, dlatego włączam kontrolę wersji i wrzucam pliki do „first commit”. I po tych czynnościach naciskam kombinację klawiszy Ctrl+Alt+Shift+K i dzieje się magia, klasa javowa konwertuje się do Kotlina i daje nam taki rezultat.

package pl.digitalzombielab.dayviewdemo

import android.os.Bundle
import android.support.design.widget.FloatingActionButton
import android.support.design.widget.Snackbar
import android.support.v7.app.AppCompatActivity
import android.support.v7.widget.Toolbar
import android.view.View
import android.view.Menu
import android.view.MenuItem

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        val toolbar = findViewById(R.id.toolbar) as Toolbar
        setSupportActionBar(toolbar)

        val fab = findViewById(R.id.fab) as FloatingActionButton
        fab.setOnClickListener { view ->
            Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG)
                    .setAction("Action", null).show()
        }
    }

    override fun onCreateOptionsMenu(menu: Menu): Boolean {
        // Inflate the menu; this adds items to the action bar if it is present.
        menuInflater.inflate(R.menu.menu_main, menu)
        return true
    }

    override fun onOptionsItemSelected(item: MenuItem): Boolean {
        // Handle action bar item clicks here. The action bar will
        // automatically handle clicks on the Home/Up button, so long
        // as you specify a parent activity in AndroidManifest.xml.
        val id = item.itemId


        if (id == R.id.action_settings) {
            return true
        }

        return super.onOptionsItemSelected(item)
    }
}

Z racji tego, że Kotlin pracuje na jvm, możemy w ramach jednego projektu łączyć klasy Javy i Kotlina, ale akurat tutaj nie będziemy tego robić. Bieżące zmiany zostały zaccomitowane jako „auto convert to Kotlin”.

Próbujemy uruchomić aplikację i.. nie uruchamia się. Brakuje jeszcze jednej rzeczy, skonfigurowania Kotlina w projekcie. Aby to zrobić ponownie uruchamiamy akcje, wpisujemy „Configure Kotlin in Project” wyskoczy nam okienko w którym ja wybieram wszystkie moduły, ponieważ chcę aby zarówno demo jak i biblioteka pracowały z Kotlinem.

Rozpoczęcie implementacji, różnice pomiędzy aplikacją a biblioteką

Po tej operacji odrobinę zmieniają nam się pliki gradle, tzn w modułach aplikacji i biblioteki dochodzi

apply plugin: 'kotlin-android'

...

    compile "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version"
}
repositories {
    mavenCentral()
}

Z kolei w głównym pliku gradle dla projektu

buildscript {
    ext.kotlin_version = '1.1.1'
    repositories {
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:2.3.0'
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"

To wszystko co musimy zrobić. Po kliknięciu przycisku debug aplikacja się nam uruchomi, tym razem już bez niespodzianek. Bieżące zmiany zostały zaccomitowane jako „configure Kotlin in project”.

To by było tyle na dzisiaj, udało nam się skonfigurować środowisko pod pracę z Kotlinem, a następnym razem będzie trochę więcej mięska, bo zaczniemy implementację biblioteki i przyjrzymy się trochę temu, jak wygląda ten język. A jest na co popatrzeć 😉

Dlaczego przycisk „read later” nigdy nie działa właściwie i co z tym zrobić – DSP#03

W internecie jest tyle dobrego! Liczba serwisów, blogów i treści technologicznych jest zatrważająca. Konkurs DSP to prawdziwa fabryka kontentu. Jest też dużo złego, ale w tym wpisie chcę się skupić na wartościowych treściach. Tych, które naprawdę chcemy przeczytać. W tym miejscu wypada też podziękować drogi czytelniku, że wybrałeś akurat ten wpis. Postaram się sprostać zadaniu które postawiłem sobie w tytule i sprawić, żeby treści były czytane, a nie odkładane na później. Bo to prawdziwy problem, artykuł świetnej jakości, który naprawdę mieliśmy ochotę przeczytać, ale akurat nie ma czasu. Ale na szczęście jest jakże pomocny przycisk „read later”. Czy aby na pewno?

tl;dr Odkładanie artykułów do przeczytania na później nie działa, IMO należy czytać je od razu, albo:

  • utworzyć sobie z takiego artykułu zadanie, odpowiadając na pytanie „po co” chcemy go przeczytać
  • zostawić artykuł w denerwującym miejscu, gdzie jak najszybciej będziemy chcieli go przeczytać i stamtąd usunąć
  • utworzyć cykliczne zadanie czytania artykułów (np 1 godzina raz w tygodniu) i wtedy czytać wszystkie oczekujące, dodanie do oczekujących nie może być proste

przeczytaj później nie działa

Analiza

Na początek kilka statystyk. Opierając się na oficjalnym blogu Pocket i podsumowaniu roku 2015, oraz na tym wpisie-wywiadzie (według tego co udało mi się wygrzebać pomiędzy tymi postami jest około 6mcy różnicy) udało mi się ustalić kilka faktów. Dane dotyczą roku 2015:

  • liczba użytkowników Pocket: ~20 000 000
  • liczba godzin poświęconych na czytanie w aplikacji: ~22 000 000
  • liczba rzeczy zapisanych „na później”: 787 187 792

Gdyby każdy użytkownik korzystał z aplikacji Pocket do czytania taką samą ilość czasu, było by to około 1h i 6m w ciągu roku. Dodatkowo każdy w tym czasie miałby do przeczytania około 39 artykułów („na później”/użytkownicy). Załóżmy, że przeczytanie średniej długości artykułu zajmuje około 5 minut, razy liczba artykułów na osobę daje nam 195 minut. A tymczasem statystyczny użytkownik czytał tylko przez 66 minut. Brakujący czas to zatem ponad dwie godziny. Powyższe dane i równania są bardzo uproszczone, ale wynika z nich, że jeśli nasz artykuł trafi do Pocket, to ma około 30% szans na to, że zostanie odczytany.

Geneza

Dlaczego tak właściwie ten procent jest tak niski? Czyżby użytkownicy dodawali sobie do list treści, którymi wcale nie są zainteresowani? Nie wydaje mi się, w czasach kiedy w internecie jest tyle dobrego, nikt raczej nie marnował by na to czasu. W mojej opinii na problem ma wpływ kilka czynników. Jednym z głównych jest to, że jesteśmy zbieraczami. Według statystyk Steam z 2014 roku 37% pobranych gier nigdy nie zostało nawet uruchomionych. Więc w jakim celu zostały kupione? Podobnie wydawnictwo Packt codziennie rozdaje jedną książkę za darmo. Pobrałem już kilkanaście, zgadnijcie jaką okrągła liczbę z nich przeczytałem? Podpowiem trochę, dzielenie przez nią rzuca wyjątkiem /byzero exception. Pewnie nie jestem w tym osamotniony? 😉 Drugim istotnym czynnikiem jest prokrastynacja. Najprościej będzie mi to opisać na przykładzie, trafiamy na jakiś warty przeczytania wpis, np ten. Nasz mózg mówi nam, to jest dobre, zapoznaj się z tym! Tymczasem śpieszymy się, albo w feedzie na facebooku, albo jakimkolwiek zostało jeszcze tyle przewijania! W związku z tym – klik – magiczny guzik, na później. Z tego co pamiętam. ja zawsze w tej sytuacji czułem ulgę. O tak, zapisałem na później, teraz już nie zginie, kiedyś na pewno przeczytam.

Definicja

Cały problem polega na tym, że bardzo dużo wartościowego kontentu się marnuje, ponieważ trafia do list „na później”, których już nigdy nie opuszcza. Jakiś czas temu wypracowałem sobie metodę, jak radzić sobie z tym problemem. Lojalnie ostrzegam – metoda nie jest prosta, wymaga trochę dyscypliny i nie jestem w stanie do niej dostarczyć żadnego, prostego przycisku.

Rozwiązanie

U mnie proces przebiegł następująco. Dodawałem wpisy do pocket. Bardzo lubiłem to narzędzie – klik i wpis czekał aż znajdę chwilę czasu dla niego. O następny, ciekawy – klik. Minęło błogie kilka miesięcy, a ja dostrzegłem rysę w tym pięknym systemie. Nigdy nie zaglądałem do Pocket aby czytać. Któregoś dnia postanowiłem coś z tym zrobić, przejrzałem wszystkie rzeczy które tam były, część przeczytałem, część usunąłem, część przeniosłem do zakładek w komputerze (nie było łatwo, zajęło kilka godzin). Ale nie tak wprost z tymi zakładkami. Specjalny folder, a w folderze podział na język artykułu. Odinstalowałem Pocket, niestety to nie rozwiązało problemu i w sieci w dalszym ciągu były ciekawe artykuły. Po jakimś czasie dotarło do mnie, że te najlepiej czytać od razu. Trafiłem, czytam to takie proste. A co jeśli artykuł jest na 20 minut, a ja mam tylko 3? Czytanie od razu nie jest możliwe w takiej sytuacji, ale trzeba sobie zadać jedno ważne pytanie. Dlaczego chcę to przeczytać? Jaką wiedzę zyskam po przebrnięciu przez tą treść? Czyż by był to tekst o powiedzmy.. SEO? Więc chcę go przeczytać, bo chcę poprawić pozycję swojej strony w wyszukiwarce. A więc mogę z tego zrobić zadanie, które trafi do mojego TODO-toolsa. W którym to praktycznie wykorzystam nabytą wiedzę, a na dodatek zrobię to w momencie, w którym będzie to dla mnie wygodne. Czyż nie o to chodzi? Noo dobra, ale ja nie chcę z tego artykułu nic wyciągnąć, chcę go tylko przeczytać, bo tak. Co wtedy? Noo cóż, moja przeglądarka ma opcję „kontynuuj, gdzie skończyłem”, czyli ładuję mi taki sam zestaw kart z jakim została wyłączona. Staram się mieć tam porządek, ale jeśli aktualnie nie mogę czegoś przeczytać, to zostaje jako otwarta karta. Taki – wyrzut sumienia, który należy się zapoznać i zamknąć. Innym sposobem jest kolekcjonowanie ich w jednym miejscu i cykliczne zadanie zaglądania tam. Inaczej to nie zadziała. Albo konkretne zadanie: przeczytaj ten artykuł. Tutaj jeszcze jedna uwaga, proces dodawania artykułu na później (do zakładek) musi być utrudniony. To zadanie nie może być przyjemne, musi wymagać podjęcia decyzji, a najlepiej kilku, żeby była to naprawdę ostateczność. U mnie jest to dodanie do zakładek, gdzie muszę wybrać odpowiedni folder, kosztuje to przynajmniej kilka kliknięć. Dlatego artykuły do zakładek trafiają bardzo rzadko.

przeczytaj później nie działa

W żadnym razie nie chcę demonizować aplikacji „Pocket” (której użyłem jako przykład), ani pokrewnych. Może po prostu ja nie umiem z nich korzystać, a ludzie radzą sobie dobrze? Nie wykluczam takiej możliwości. Ale jeśli u Ciebie również leży stos nieprzeczytanych artykułów to przejrzyj je. Zastosuj się do powyższych wskazówek i zobaczysz, że od razu poczujesz się lepiej. Bo usuniesz z dna swojej głowy problem, problem ogromu nieprzeczytanych artykułów. Wreszcie je uporządkujesz i będziesz mógł konkretnie coś z nich wynieść. A o to chyba chodzi, kiedy decydujemy się poświęcić im swój czas, prawda?