#12 Animacja zmiany aktywności

W poprzednich częściach tej serii zatrzymaliśmy się na tym, że jeden kolor wypełnia cały ekran. Dzisiaj pójdziemy krok dalej, czyli opiszę jak zrobić płynne przejście pomiędzy jedną, a drugą aktywnością.

Oto kroki które chcemy wykonać po wypełnieniu ekranu kolorem zieleni lub czerwieni:

1) płynna zmiana koloru na biały
2) jednoczesna animacja obiektu, zamiana symbolu „wykonane” na strzałkę
3) jednoczesna zmiana przycisku fab
4) kiedy już kolor zmieni się na biały usunięcie animacji przejścia pomiędzy aktywnościami
5) start nowej aktywności z animacją pojawiania się

Metoda, które wykonuje pierwsze cztery operacje wygląda tak:

MorphingAnimation morphingAnimationView = new 
    MorphingAnimation(getApplicationContext(), successV);
morphingAnimationView.animImageView(R.drawable.ic_done_to_line);
animateFab(R.drawable.ic_done_to_line, false);
ValueAnimator colorAnim = ObjectAnimator.ofInt(
    layoutSuccess, "backgroundColor",
    getResources().getColor(R.color.colorSuccessGreen), 
 getResources().getColor(R.color.icons));
colorAnim.setDuration(500);
colorAnim.setEvaluator(new ArgbEvaluator());
colorAnim.start();
colorAnim.addListener(new AnimatorListenerAdapter() {
    @Override
    public void onAnimationEnd(Animator animation) {
        super.onAnimationEnd(animation);
        Intent intent = new Intent(NumbersOrderActivity.this, 
      NumbersCompareActivity.class);
        startActivity(intent);
        overridePendingTransition(0, 0);
    }
});

Nowością która się tutaj pojawia jest klasa ValueAnimator. Klasa akurat w tym przypadku pozwala na płynną zmianę kolorów obiektu, z zielonego na biały. Na klasie jest założony listener, który po zakończeniu animacji uruchamia nową aktywność, z kolei metoda overridePendingTransition(0, 0) powoduje usunięcie domyślnej animacji otwarcia nowego activiti. Chciałem umieścić animację rozjaśniania w tym miejscu, ale zadziałało by to tak, że rozjaśniał by wszystko łącznie z paskiem i przyciskiem fab, a tego nie chciałem, celem było aby zmieniała się tylko zawartość ekranu. Przyjmowane przez metodę parametry to zero i zero co oznacza usunięcie animacji, pierwszy z tych parametrów, to animacja włączenia nowego activiti, drugi to wyłączenie starego. Jest też tutaj pewna asynchroniczność, mianowicie czas zmiany kolou to 500ms, z kolei animacja zmiany kształtu trwa 200ms, możliwe, że jeszcze w jakiś sposób to zmienię, ale na ten moment wygląda chyba nieźle.

Ostatnią istotną rzeczą jest kod, którego użyłem w nowej aktywności, wykonuje on punkt 5, czyli odpowiada za animację rozjaśnienia i jest uruchamiany podczas tworzenia activity, dzięki temu mamy animację już od samego początku. Po stronie javy prezentuje się tak:

Animation animFadeIn = AnimationUtils.loadAnimation(
    getApplicationContext(), R.anim.fade_in);
numbersCompareActivity.startAnimation(animFadeIn);

Po stronie xml w ten sposób:

<alpha
    android:duration="@integer/anim_duration"
    android:fromAlpha="0.0"
    android:interpolator="@android:anim/accelerate_interpolator"
    android:toAlpha="1.0" />

Wydaje mi się, że jest samo-opisywalny i nie za wiele mogę tutaj powiedzieć 😉 A tak wygląda efekt finalny który udało mi się uzyskać:

Dodaliśmy kolejną funkcję, więc czas na rozszerzenie klasy bazowej, aby nie pisać ciągle tego samego. Ale jak bym to widział, opisze już następnym razem 😉
Pozdrawiam!

#10 Circular reveal animation

W poprzednim wpisie pokazałem jak działa aktywność dodawania, ale w jednym miejscu nie było w ogóle animacji, co skutkowało dziwnym pojawianiem się obiektów. Dzisiaj zajmiemy się dalej tym oknem i dodamy tą animację.

Po poprawkach aktywność  działa w ten sposób, że po kliknięciu na strzałkę sprawdza, czy wartość jest prawidłowa. Na tej podstawie rozpoczyna animację wypełniania widokiem sukces:

lub porażka

Widoki te są zdefiniowane jako osobne layouty, z ustawionym parametrem hide. Po zakończeniu wypełniania w tle ustawia wartości dla kolejnego zadania, tj liczba belek na stosie, oraz uzupełnia gwiazdkę, w zależności od wyniku poprzedniego działania. Po kliknięciu na środek ekranu cofa animację i wyświetla widok z nowymi wartościami. Dzisiaj chciałem się skupić właśnie na tej animacji wypełniania ekranu zawartością.

Nazywa się ona circular reveal i pojawiła się razem z Material Design, choć aktualnie znajduje się już w support library i możemy z niej korzystać w starszych wersjach. Metoda tworząca tą animację przyjmuje aż 5 parametrów:
– widok na którym wywołujemy animację
– środek tego widoku na którym wywołujemy animację (współrzędne x, y)
– punkt startowy i końcowy promienia animacji.

Oto jak wygląda ta animacja, ustawiłem czas trwania na 4 sekundy:

Kodzik, który obsługuje tą animację w przypadku prawidłowej odpowiedzi wygląda tak:
morphAnim.animFab(R.drawable.ic_line_to_done, true);
Animator anim = ViewAnimationUtils.createCircularReveal(layoutSuccess,
        centerX, centerY, startRadius, endRadius);
layoutSuccess.setVisibility(View.VISIBLE);
anim.addListener(new AnimatorListenerAdapter() {
    @Override
        public void onAnimationEnd(Animator animation) {
            super.onAnimationEnd(animation);
            addStar(counter == result);
            createReadyView();
        }
    });
anim.start();
Ważną rzeczą jest ustawienie parametru visibility dla layoutu który ma przykryć nasz główny layout. Jeszcze parametry które wykorzystujemy w tej metodzie:
centerX = contentNumbersBricksAdding.getRight();
centerY = contentNumbersBricksAdding.getBottom();
double valueToSqrt =
Math.pow((double) contentNumbersBricksAdding.getWidth(), 2)
+ Math.pow((double) contentNumbersBricksAdding.getHeight(), 2);
endRadius = (int) Math.sqrt(valueToSqrt);

Pobieramy X i Y, czyli środek koła z którego będzie rozpoczynała się animacja, ustawiamy startRadius na 0, oraz obliczamy endRadius z wzoru na przekątną prostokąta, czyli tego wzoru:

Dla animacji powrotnej, zamieniamy po prostu punkt startowy i końcowy promienia, czyli startRadius z endRadius. Oto jaki efekt uzyskujemy finalnie, po ustawieniu animacji do normalnej szybkości:
Ps. To już 10 wpis, czyli jesteśmy na półmetku w konkursie DSP 😉
Pozdrawiam!

#8 Biblioteka ButterKnife, czyli najwyższy czas zacząć pisać mniej kodu

Standardowy kod, który trzeba napisać to rzecz, która sprawia radość tylko jak się pierwszy raz programuje 😉 Człowiek jest pełen pasji, odkrywa nowe rzeczy i wcale mu nie przeszkadza, że generuje lub pisze kolejny setter, czy powiela linijkę i zmienia tylko nazwy. Z czasem jednak coś zaczyna uwierać. Brzydko to potem wygląda, leży najczęściej na spodzie klasy, nikt tego nie czyta (bo i po co czytać auto-wygenerowane przez środowisko, gettery, settery itd). W Androidzie mamy jeszcze kilka rzeczy ponad standardowe, javowe instrukcje. A jak powszechnie wiadomo, co swoją drogą opisał już dawno temu Einstein:

No dobra, może Einstein nie koniecznie to miał na myśli, ale pasuje bardzo dobrze 😉 Im więcej kodu, tym większa szansa, że nie zauważamy czegoś ważnego. Czasem w ogóle zdarza się zapomnieć czegoś wygenerować i problem gotowy. Z czasem zaczyna się po prostu nie chcieć tego wszystkiego pisać. A jak wiadomo, lenistwo jest motorem postępu, dlatego opiszę bibliotekę Butter Knife, która pozwoli nam zaoszczędzić kilka linijek, i bardzo ułatwi późniejsze czytanie kodu. Biblioteka nie jest jakąś super nowością na rynku, nie mniej wato docenić to co oferuje.
Instalacja tej biblioteki to po prostu kwintesencja lenistwa, w pliku build.gradle, w module aplikacji:
compile 'com.jakewharton:butterknife:7.0.1'
Ewentualnie podbijamy wersję, jeśli pojawi się jakaś aktualizacja albo instalujemy wersję beta, jak ktoś lubi 😉
Pierwszą rzeczą, którą możemy wyeliminować jest zapis:
TextView stackCounterTV = (TextView) findViewById(R.id.bricksTV);
Czyli standardowy androidowy zapis łączący elementy widoku z plików xml z obiektami. Niby nic wielkiego, ale pamiętajmy, że trzeba go umieszczać w metodzie cyklu życia, najczęściej onCreate(), albo przynajmniej stamtąd wywoływać. A jak wiemy, obiektów dziedziczących po View możemy mieć w aktywności całkiem sporo. Dzięki Butter Knife można skorzystać z adnotacji:
@Bind(R.id.bricksTV) TextView stackCounterTV;
definiując nasz obiekt.
Z kolei w metodzie onCreate powinno być jeszcze wywołane (zresztą, za każdym razem kiedy korzystamy z Butter Knife):
ButterKnife.bind(this);
Ok, przy pierwszym przykładzie mamy taką samą ilość kodu, ale znacznie zmienia się jakość i czytelność. Widoki których będziemy używać w całej klasie mamy już zdeklarowane na samym początku, nie musimy już z nimi nic dodatkowo robić w metodach cyklu życia, możemy normalnie z nich korzystać. Kolejny przykład jest bardziej sexi:
@Bind({R.id.starIV1, R.id.starIV2, R.id.starIV3, R.id.starIV4, R.id.starIV5})
ImageView[] starContainer;
Pobrane obiekty wpakowujemy od razu do tablicy, nie musimy ich nigdzie deklarować, dzięki czemu zaoszczędzamy już 6 linii standardowego kodu.
Dalej spójrzmy sobie na:
@OnClick(R.id.addingPlusBtn)
   void add() { }

Nie musimy pisać metody onClick w xml, ani dodawać listenera na obiekcie. To wszystko, co potrzeba, żeby kliknięcie zadziałało. Możemy sobie jeszcze wywołać @onLongClick, do długich kliknięć, albo @onItemClick dla elementów typu listView.
Biblioteka radzi sobie także z fragmentami, trzeba jednak trochę zmienić standardową metodę na:
ButterKnife.bind(this, view);

Jeszcze kilka ciekawych funkcji z tej biblioteki:

@BindString(R.string.title) String title;
@BindDrawable(R.drawable.graphic) Drawable graphic;
@BindColor(R.color.red) int red;

Czyli pobranie grafiki, koloru, albo napisów w aktualnym języku (o ile jest obsłużony, jeśli nie, pobiera w języku aplikacji). Jak widać, znowu oszczędzamy sporo linii.

Wciąż mało? 😉
To na koniec coś dla ekstremalnie leniwych, czyli dodatek, który po kliknięciu skrótu „Generate code” wygeneruje nam to co potrzebujemy 😉 Nie działa w aktualnej wersji Android Studio, ale autor pracuje nad poprawką. Dodatek nazywa się android-butterknife-zelezny i znajduje się tutaj i tutaj.

Wypisałem najważniejsze możliwości, które daje nam biblioteka Butter Knife. Patrząc obiektywnie, nie ma tego dużo. Korzystanie z niej jest dzięki temu bardzo proste, intuicyjne, próg wejścia jest niski, a korzyści naprawdę spore. Osobiście bardzo polecam 😉
Pozdrawiam!

#7 Porównywanie stosów

W dwóch ostatnich postach pokazywałem jak zrobić animację na ścieżkach. Dzisiaj pokażę w jaki sposób wykorzystałem te animację.

Efekt który uzyskałem prezentuje się w ten sposób:

Chodziło o to, aby zwrócić uwagę dzieciaków na to, kiedy ilość jest w równowadze. Tzn, jeśli po lewej mamy tyle samo obiektów jak po prawej, to kolor FAB zmienia się na zielony. Jeśli zmieniamy te ilości przycisk odpowiednio reaguje.
Kodzik do tego nie jest zbytnio skomplikowany, wszystko dokładnie można obejrzeć na gicie, wkleję tylko kawałki:
public void onClickCompareOperations(View view) {
    switch (view.getId()) {
        case R.id.plusLeftBtn:
            if (counterLeft < 10) {
                counterLeft++;
                stackBricksViewGroup.refreshView(counterLeft, bricksLeftArray, 
      getApplicationContext());
            }
            break;
        case R.id.minusLeftBtn:
            if (counterLeft > 0) {
                counterLeft--;
                stackBricksViewGroup.refreshView(counterLeft, bricksLeftArray,
              getApplicationContext());
            }
            break;
        case R.id.plusRightBtn:
            if (counterRight < 10) {
                counterRight++;
                stackBricksViewGroup.refreshView(counterRight, bricksRightArray, 
      getApplicationContext());
            }
            break;
        case R.id.minusRightBtn:
            if (counterRight > 0) {
                counterRight--;
                stackBricksViewGroup.refreshView(counterRight, bricksRightArray, 
     getApplicationContext());
            }
            break;
    }
    if(counterLeft!=counterRight&&firstChange) {
        morphAnim.animFab(R.drawable.ic_done_to_undone, false);
        firstChange=false;
    }
    if(counterLeft==counterRight) {
        morphAnim.animFab(R.drawable.ic_undone_to_done, true);
        firstChange=true;
    }
}

Jak widać po każdej zmianie sprawdzamy co było kliknięte, odświeżamy odpowiedni widok i odświeżamy fab.

public void animFab(int resId, boolean success) {
    final Animatable icon = (Animatable) ResourcesCompat.getDrawable(context, resId);
    floatingActionButton.setImageDrawable((Drawable) icon);
    icon.start();
    if(success)
        floatingActionButton.setBackgroundTintList(context.getResources()
            .getColorStateList(R.color.colorSuccessGreen));
    else
        floatingActionButton.setBackgroundTintList(context.getResources()
     .getColorStateList(R.color.colorFailureRed));
}
Sama animacja Fab to również tylko kilka linijek kodu, ponieważ lwią pracę ostatnio odwaliliśmy po stronie xmla.
Następnym krokiem będzie stworzenie aktywności, która będzie sprawdzała po kliknięciu, czy stosy są w równowadze i wyświetlała tą informację.
Pozdrawiam!

#6 Morphing – animujemy floating action button cz.2 ścieżki

W poprzedniej części opisałem jak stworzyć animację morphingu bez jednego aspektu, mianowicie ścieżek, czyli rzeczy zgoła najpotrzebniejszej. Dlatego dzisiaj skupimy się na ścieżkach.

Przykładowa ścieżka wygląda tak:

M9,16.2 L4.8,12 L3.4,13.4 L9,19 L21,7 L19.6,5.6 L9,16.2 Z

W języku svg tak:

<svg fill="#FFFFFF" height="48" 
        viewBox="0 0 24 24" width="48" 
  xmlns="http://www.w3.org/2000/svg">
    <path d="M0 0h24v24H0z" fill="none"/>
    <path d="M9 16.2L4.8 12l-1.4 1.4L9 19 21 7l-1.4-1.4L9 
     16.2z"/>
</svg>

Z kolei jako png tak (kliknij aby zobaczyć):

Główna zasada tworzenia ścieżek do morphingu jest taka, że muszą być kompatybilne, tzn muszą być stworzone z takiej samej ilości elementów, czyli liter i cyfr przy nich, poukładanych w takiej samej kolejności, różniących się oczywiście zawartością, np:
<string name="path_undone">M 19.0,6.41 L 17.59,5.0 L 12.0,10.59 L 6.41,5.0 L 5.0,6.41 L 10.59,12.0 L 5.0,17.59 L 6.41,19.0 L 12.0,13.41 v 0.0 l 0.0,0.0 l 0.0,0.0 v 0.0 c 0.0,0.0,0.0,0.0,0.0,0.0 c 0.0,0.0,0.0,0.0,0.0,0.0 L 17.59,19.0 c 0.0,0.0,0.0,0.0,0.0,0.0 c 0.0,0.0,0.0,0.0,0.0,0.0 L 19.0,17.59 m 0.0,0.0 L 13.41,12.0 c 0.0,0.0,0.0,0.0,0.0,0.0 c 0.0,0.0,0.0,0.0,0.0,0.0 v 0.0 l 0.0,0.0 l 0.0,0.0 v 0.0 c 0.0,0.0,0.0,0.0,0.0,0.0 c 0.0,0.0,0.0,0.0,0.0,0.0 L 19.0,6.41 Z</string>
<string name="path_repeat">M 12.0,6.0 L 12.0,6.0 L 12.0,6.0 L 12.0,6.0 L 12.0,6.0 L 12.0,6.0 L 12.0,6.0 L 12.0,6.0 L 12.0,6.0 v 3.0 l 4.0,-4.0 l -4.0,-4.0 v 3.0 c -4.42,0.0,-8.0,3.58,-8.0,8.0 c 0.0,1.57,0.46,3.03,1.24,4.26 L 6.7,14.8 c -0.45,-0.83,-0.7,-1.79,-0.7,-2.8 c 0.0,-3.31,2.69,-6.0,6.0,-6.0 L 12.0,6.0 m 6.76,1.74 L 17.3,9.2 c 0.44,0.84,0.7,1.79,0.7,2.8 c 0.0,3.31,-2.69,6.0,-6.0,6.0 v -3.0 l -4.0,4.0 l 4.0,4.0 v -3.0 c 4.42,0.0,8.0,-3.58,8.0,-8.0 c 0.0,-1.57,-0.46,-3.03,-1.24,-4.26 L 18.76,7.74 Z</string>

Widzimy tutaj sporo 0, ale dzięki temu uzyskujemy kompatybilność, a efekt który dają nam te ścieżki w zwolnionym tempie wygląda tak:

Noo dobra, wytłumaczyłem jak to musi wyglądać, teraz jak transformować pliki svg na ścieżki? Na początku trafiłem na stronę svg2android. Jeśli wrzucimy tam plik svg, i zaznaczymy opcję:
„Bake transforms into path (experimental)”
to otrzymamy poprawną ścieżkę. To samo robimy z drugim plikiem do którego chcemy morphować i voilla. Chociaż nie do końca, bo jak pisałem powyżej w zależności od złożoności ikony możemy otrzymać różne długości ścieżek i trzeba to wyrównać, można to zrobić oczywiście ręcznie jeśli ścieżka jest krótka jak tutaj:
 
<string name="path_line">M22,12 L18,8 L18,11 L3,11 L3,13 L18,13 L18,16 Z</string>
<string name="path_done">M9,16.2 L4.8,12 L3.4,13.4 L9,19 L21,7 L19.6,5.6 L9,16.2 Z</string>

Które to ścieżki dają nam taki efekt:

Jest jednak szybszy sposób 😉
Biblioteka vectalign, pobierany sobie stamtąd plik .jar, uruchamiamy go i podajemy paramtery –start nazwa pierwszego svg, –end nazwa drugiego svg tak wygląda efekt:
Kopiujemy sobie ścieżki z pliku wynikowego i uzyskujemy bardzo ładną animację, np taką:
Dla porządku dodam jeszcze ścieżki dla tej konkretnej animacji:
 
<string name="path_line">M22,12   L18,8  L18,8  L18,11  L18,11  L3,11 L3,11  L3,13  L3,13 L18,13  L18,13  L18,16 Z</string>
<string name="path_undone">M19,6.41 L17.59,5 L12,10.59 L6.41,5 L5,6.41 L10.59,12 L5,17.59  L6.41,19  L12,13.41 L17.59,19 L19,17.59 L13.41,12 Z</string>
I to już wszystko co udało mi się odkryć w temacie morphingu, tak naprawdę dowolną ikonę możemy przerobić na svg, a następnie na ścieżkę, dlatego mam wrażenie że w projekcie jeszcze pojawi się tego typu animacja nie raz i to niekoniecznie na tak małych obiektach jak floating action button 😉
Pozdrawiam!

#5 Morphing – animujemy floating action button cz.1

Razem z pojawieniem się Material Design, Androida Lollipop wprowadzono nowy sposób przechowywania ikon. Zamiast plików graficznych jak np png postawiono na zgodny z ideą flat design plik typu svg. Nietrudno zrozumieć taką decyzję:

xxxhdpi to rozmiar dla fullHd, ileż jeszcze można tych x dodać, żeby obsługiwać 4k, albo w przyszłości 16k? 😉 Svg ma ten plus, że skaluje się bez utraty jakości, tzn jest jeden plik, który obsłuży wszystkie rozdzielczości. Dla bardziej zainteresowanych standardem, który opracował W3C zresztą link.

Aplikacji które korzystały by jednak z tego typu rozwiązań jakoś błyskawicznie nie przybywa (png znajdujący się w folderze xxxhdpi przeskaluje się na ekranie 4k, a że smartfony mają rozmiar jakieś 6-7 cali te większe, tablety jakieś 10 cali, to i tak tego gołym okiem nie widać. Może na telewizorze 4k to widać, jak się patrzy z bliska, ale nie po to są telewizory, żeby patrzeć z bliska ;). Dlatego też pliki png mają się dobrze. Zaletą stosowania svg jest możliwość korzystania z morphingu, który na png musielibyśmy wywołać jakimś zewnętrznym programem, przerabiając go zresztą na gifa. No i tak ten gif by się potem skalował na większy ekran.. Ok, wiemy już, że powinniśmy korzystać z svg. Samo korzystanie jest takie samo jak z png w Androidzie, wrzucamy plik tylko do jednego folderu: drawable, a widać go wszędzie. A teraz zabierzmy się za morphing, ufff trochę czasu mi zajęło rozgryzienie tego.

Aby móc użyć animacji morphingu na androidzie poniżej wersji 5.0 skorzystałem z biblioteki vector-compat, która dostarczyła kilka gotowych animacji:

Nie pasowały one jednak do tego co chciałem uzyskać, dlatego też zrobiłem kilka swoich. Oto jak się to robi w androidzie.
Zacznę może od kodu w javie, musimy odnieść się do pliku xml typu animated vector:
final Animatable icon = (Animatable) ResourcesCompat.getDrawable
      (getApplicationContext(), R.drawable.ic_line_to_done);
fab.setImageDrawable((Drawable)icon);
icon.start();
Zawartość tego pliku (ic_line_to_done.xml) to:
<?xml version="1.0" encoding="utf-8"?>
<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:drawable="@drawable/ic_line_vector">
    <target
        android:name="rotationGroup"
        android:animation="@anim/line_to_done_rotation" />
    <target
        android:name="v"
        android:animation="@anim/line_to_done_path" />
</animated-vector>
Gdzie widzimy 3 istotne rzeczy:
– android:drawable, czyli lokalizację opisu naszej początkowej ikony, tej od której będzie zaczynała się animacja
– target totationGroup, czyli odnośnik do aimacji rotacji, przed zmianą ikony następuje obrócenie aktualnej, taki efekt dodający fantazji 😉
-target v, czyli właściwy odnośnik do naszej animacji morphingu
Plik line_to_done_rotation, czyli nasz obrót:
<?xml version="1.0" encoding="utf-8"?>
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="@integer/anim_duration"
    android:propertyName="rotation"
    android:valueFrom="0"
    android:valueTo="360"/>
Nic trudnego, po prostu zwykły obrót ikony, trwający 200ms. Plik line_to_done_path, czyli zmiana naszych ścieżek:
<?xml version="1.0" encoding="utf-8"?>
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"

    android:duration="@integer/anim_duration"
    android:propertyName="pathData"
    android:valueFrom="@string/path_line"
    android:valueTo="@string/path_done"
    android:valueType="pathType"
    app:vc_valueType="pathType"/>
Tutaj również jest łatwo, zwracam tylko uwagę na app:vc_valueType którego potrzebuje vector_comapt, żeby działać na starszych urządzeniach.
No i plik drawable, jest typu vector, oto jak wygląda:
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:width="48dp"
    android:height="48dp"
    android:viewportHeight="24"
    android:viewportWidth="24"

    app:vc_viewportHeight="24"
    app:vc_viewportWidth="24">
    <group
        android:name="rotationGroup"
        android:pivotX="12.0"
        android:pivotY="12.0"
        android:rotation="360.0">

        <path
            android:name="v"
            android:fillColor="#FFFFFF"
            android:pathData="@string/path_line"

            app:vc_fillColor="#FFFFFF"
            app:vc_pathData="@string/path_line" />
    </group>
</vector>
Mamy tutaj grupę w której znajduje się plik path, czyli tak jak pisałem powyżej ścieżka opisująca wygląd naszej ikony. I przechodzimy do rzeczy najtrudniejszej, czyli właśnie ścieżek, oto jak przykładowo wyglądają:
M 48.0,54.0 L 31.0,42.0 L 15.0,54.0 L 21.0,35.0 L 6.0,23.0 L 25.0,23.0 L 25.0,23.0 L 25.0,23.0 L 25.0,23.0 L 32.0,4.0 L 40.0,23.0 L 58.0,23.0 L 42.0,35.0 L 48.0,54.0 
 
M 48.0,54.0 L 48.0,54.0 L 48.0,54.0 L 48.0,54.0 L 31.0,54.0 L 15.0,54.0 L 10.0,35.0 L 6.0,23.0 L 25.0,10.0 L 32.0,4.0 L 40.0,10.0 L 58.0,23.0 L 54.0,35.0 L 48.0,54.0
Mam nadzieję, że zaciekawieni, w następnej części opiszę jak uzyskać prawidłowe ścieżki z ikon znajdujących się tutaj. Bez tłumaczenia co oznaczają te liczby i litery, skupię się po prostu na tym w jaki sposób te ścieżki uzyskać i móc morfować dowolne ikony 😉

Finalnie uzyskałem takie efekty.

Pozdrawiam!

#4 Hakuna Matata, czyli wprowadzenie do języka Suahili

Jak pisałem już wcześniej, podczas tworzenia aplikacji musimy skupić się na matematyce, oraz na nauce języka Suahili (żeby to chociaż był angielski ;). No ale cóż, pora trochę zapoznać się z tym językiem. Wybór dla tamtego obszaru jest raczej oczywisty, jest to jeden z najpopularniejszych języków w całej Afryce, posługuje się nim około 50 mln osób. Można było się z nim zetknąć w książce „W pustyni i w puszczy” lub w filmie: „Król Lew”. Całe szczęście, że obecnie jest on opisywany alfabetem łacińskim, kiedyś używano arabskiego 😉 Suahili w języku Suahili nazywa się kiswahili.

Tytułowe Hakuna Matata znaczy: Nie ma problemu 😉 Język ten jest naprawdę dźwięczny i ma mnóstwo zwrotów, które w Polsce wydają się śmieszne. Wkrótce pewnie dodam wymowę liczenia podczas dodawania klocków, które implementowałem ostatnio, oto jak wygląda liczenie w suahili

sufuri – 0
moja – 1
mbili – 2
tatu – 3
nne – 4
tano – 5
sita – 6
saba – 7
nane – 8
tisa – 9
kumi – 10

Przeglądając zasoby internetu znalazłem jeszcze kilka fajnych zwrotów:

Piga picha mimi! – Zrób mi zdjęcie!
Choo iko wapi? – Gdzie jest toaleta?
Moja kwa moja – Prosto
Sasa hivi saa ngapi? – Która godzina?

Na ten moment nie mamy jakiegoś konkretnego planu w jaki sposób przeprowadzić naukę podstaw tego języka, chcemy dodać obrazki na których będzie zmieniał się wykonawca, czynność i obiekt który będzie podlegał czynności, ale jak zacząć, nie mamy pojęcia 😉 Będzie zabawa, mam nadzieję, że u odbiorców aplikacji również 😉

Pozdrawiam!

#3 Animacja opadania, ruch jest ważny!

Wraz z pojawieniem się Androida Lollipop w systemie Android pojawiło się sporo ciekawych animacji. Po wcześniejszych wersjach to była prawdziwa rewolucja, ludzie naprawdę potrzebowali ładnych rzeczy (swego czasu jeszcze przed Androidem 5.0 w sklepie Play przez jakiś czas bardzo popularny był kalkulator. Nie dlatego, że był super kalkulatorem, był po prostu kalkulatorem. Ale był bardzo ładny, naprawdę dopracowany pod względem designu i to wystarczyło, żeby coś tak oczywistego jak kalkulator, stało się jedną z najpopularniejszych aplikacji. Nie pamiętam jak się nazywał, to była moja własna obserwacja, ale wnioskuję, że ludzie pragnęli ślicznych aplikacji). Razem z tymi animacjami pojawiło się sporo wytycznych i przykładów jak powinny wyglądać i zachowywać się aplikacje w stylu Material Design, np tutaj. O albo taki widoczek:
Wygląda naprawdę dobrze, spróbujmy więc może zrobić chociaż namiastkę tego co tam widać. 😉
Problemem z którym musiałem się dzisiaj zmierzyć była animacja. Celem było osiągnięcie jak najbardziej „naturalnego efektu” podczas opadania klocków na stos. Chcemy, aby użytkownik odniósł wrażenie, że oto prawdziwy klocek upada, żeby był w stanie fizycznie „w życiu” sobie taką sytuację wyobrazić. Cały czas mamy w pamięci, że użytkownikami są dzieciaki, które nigdy nie miały kontaktu z technologią, nie możemy polegać na nawykach, które mają wykształcone użytkownicy z Polski, musimy takie nawyki stworzyć od nowa. Dlatego wybrałem animację odbijania, ale po kolei, najpierw troszkę logiki dodawania i odejmowania klocków
    public void refreshView(int counter, View[] bricksArray, Animation animation, boolean animOnlyLast) {
            if (counter>0&&bricksArray[counter].getVisibility() == View.INVISIBLE) {
                if(counter>1&&animOnlyLast)
                    bricksArray[counter-1].clearAnimation();
                bricksArray[counter].setVisibility(View.VISIBLE);
                animation.setInterpolator(new BounceInterpolator());
                bricksArray[counter].startAnimation(animation);
            } else {
                if(counter<9&&animOnlyLast)
                    bricksArray[counter + 2].clearAnimation();
                bricksArray[counter + 1].setVisibility(View.INVISIBLE);
                ReverseInterpolator reverseInterpolator = new ReverseInterpolator();
                animation.setInterpolator(reverseInterpolator);
                bricksArray[counter+1].startAnimation(animation);
            }

 

 
Trochę mi napisanie tego kawałka kodu czasu zajęło, już tłumaczę o co chodzi. Dostajemy licznik, czyli który klocek ma się wyświetlać (numer klocka na stosie), tablicę naszych obiektów, animację, oraz czy animować tylko ostatni element.
Główny if, licznik jest większy od zera, tutaj kolejność warunków jest ważna, jeśli bym je zamienił, to spróbował bym sprawdzić widzialność obiektu na null-u. Następnie, jeśli licznik jest większy od 1 (powód znowu null), oraz animuj ostatni element ma wartość prawda, wtedy wywołuję na obiekcie znajdującym się poniżej stosu metodę czyść animację. Jeśli bym tego nie zrobił, animował bym też tamten poprzedni obiekt. Czyli opadał by mi cały stos, zamiast tylko ostatniego klocka. Dalej już łatwo, ustaw widzialność, ustaw Interpolator (o tym poniżej, będą obrazki 😉 i uruchom animację na obiekcie.
Główny else i pierwszy if w nim, sprawdzenie czy znowu nie wywołamy wyczyszczenia animacji na nieistniejącym obiekcie (główny else oznacza tutaj ukrywanie obiektów, czyli mamy stos i przemieszczamy się w dół). Następnie oczywiście niewidzialność i odwrócona animacja na obiekcie znajdującym się nad aktualnym licznikiem. Przy odwróconej interpolacji wspomogłem się stackOverflow, okazało się, że Android nie dostarcza możliwości prostego odwrócenia animacji.
 
Jak wygląda samo tworzenie animacji w androidzie? Ano tworzymy sobie xml-a z parametrami opisującymi naszą animację, np takiego:
 
<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:fillAfter="true"
    android:interpolator="@android:anim/linear_interpolator" >

    <translate
        android:duration="500"
        android:fromYDelta="-50%p"
        android:toYDelta="0%p" />

    <alpha
        android:duration="300"
        android:fromAlpha="0.0"
        android:toAlpha="1.0" />

</set>

 

 
Możemy tutaj wrzucić jeszcze scale i rotate. Czyli możemy poruszać, obracać, powiększać/pomniejszać, pokazywać/ukrywać nasze obiekty. Do tego dodać parametry, np opóźnienie startu, kiedy chcemy mieć animację sekwencyjną, czas trwania, pozycje na ekranie, itd.. To samo możemy uzyskać w kodzie javy w klasie, ale jakoś tak tutaj mi przejrzyściej 😉
 
Jeszcze dwa słówka o tym, jak tego użyć w klasie
 
       Animation animPut = AnimationUtils.loadAnimation(getApplicationContext(),
                R.anim.put_anim);

 

Koniec dwóch słówek 😉
 
Przechodzimy do interpolatora, czyli sposobu w jaki nasza animacja ma się zachowywać. Wpisany do góry w xmlu to linear_interpolator. Klasyczny i nudny, ciągły ruch z tą samą szybkością przez cały czas, o tak to wygląda:
 
Prosty, płynny ruch, według mnie robotyczny, na pewno nie naturalny.
Spróbowałem więc następnego, nazywa się bounce i jak sama nazwa wskazuje, udaje odbijający się element. Hmmm jeśli rzucimy jakąś belkę na stos, to prawdopodobnie się odbije, wybrałem właśnie jego jako najbardziej naturalny, kojarzący się z rzeczywistością. Prezentuję:
Ładniutko, w momencie, w którym zamiast tej jednolitej belki będziemy mieli jakieś png, wydaje mi się, że będzie wyglądał w porządku. Na koniec sprawdziłem jeszcze jednego, nazywa się Overshot. Ja tam nazwał bym go plum, bo kojarzy mi się z wrzuceniem czegoś pływającego do wody 😉 Oceńcie sami:
Ciekawie wygląda, przyjmuje parametr pozwalający określić głębokość na jaką ma zejść 😉 Nie wymieniłem tutaj wszystkich interpolatorów są jeszcze np accelerate, decelerate, accelerate&decelerate.
Uff dobrnęliśmy do końca, chociaż tak naprawdę, możemy sobie stworzyć własny interpolator, i wydaje mi się, że to jeszcze nie wszystkie możliwości, jeśli chodzi o tworzenie animacji w Androidzie 😉 Chyba coś jeszcze na ten temat napiszę.
Ps. Dla osób startujących w konkursie dsp, zauważyłem, że ten konkurs sporo daje jeśli chodzi o tworzenie się społeczności. Jeśli trafiłeś tutaj w ramach konkursu, zapraszam na Slacka, toczą się tam naprawdę ciekawe rozmowy, i na bieżąco można otrzymywać recenzje swoich postów, kolejna motywująca rzecz 😉
Pozdrawiam!

#2 Nie dla metody Copiego-Paste’a, czyli ponowne użycie layoutów w Androidzie

Możliwość ponownego użycia fragmentu kodu, funkcjonalności jest jedną z wytycznych tworzenia dobrego, obiektowego oprogramowania. Znamy klasy, z których możemy dziedziczyć (btw, wiecie jaki jest najlepszy obiektowy sposób zdobycia fortuny? Dziedziczenie :D), znamy interfejsy, przeciążanie itd. Logicznie podobne podejście możemy zastosować tworząc interfejsy użytkownika w androidzie.

Zaczynamy od najmniejszego elementu który chcemy wykorzystać, czyli w przypadku mojej aplikacji, był to ogólny typ: View (przynajmniej na razie, finalnie pewnie zostanie zastąpiony jakimś ImageView). Dla tego typu View, który w aplikacji będzie symbolizował pojedynczą belkę tworzymy odpowiedni styl (styles.xml), u mnie wyglądało to tak:

    <style name=”BricksStyle” parent=”AppTheme”>
        <item name=”android:layout_width”>match_parent</item>
        <item name=”android:layout_height”>wrap_content</item>
        <item name=”android:layout_weight”>1</item>
        <item name=”android:background”>@color/colorAccent</item>
        <item name=”android:layout_margin”>5dp</item>
        <item name=”android:visibility”>invisible</item>
    </style>

Warto korzystać ze stylów nawet kiedy mamy mieć dwa takie same przyciski w aplikacji, dzięki temu zmiana jednego parametru, będzie następowała w jednym miejscu. Ja będę miał naprawdę sporo tych obiektów, dlatego chociażby zmiana parametru visibility w jednym miejscu zamiast stu, pozwala mi podejrzeć aplikację na podglądzie w android studio. Przed zbudowaniem aplikacji też wystarczy zmiana tylko w tym jednym miejscu, żeby zachować jej logikę działania. Gdybym chciał zmienić kolor belek, to znowu tylko jedna linia kodu. A oto jak wykorzystałem ten styl:

<LinearLayout xmlns:android=”http://schemas.android.com/apk/res/android”
    android:id=”@+id/container”
    android:layout_width=”match_parent”
    android:layout_height=”wrap_content”
    android:orientation=”vertical”>

    <View
        android:id=”@+id/brick10V”
        style=”@style/BricksStyle” />

    <View
        android:id=”@+id/brick9V”
        style=”@style/BricksStyle” />

    <View
        android:id=”@+id/brick8V”
        style=”@style/BricksStyle” />
…

Jak widać, zamiast do każdego obiektu View wklepywać to samo, wpisuję tylko jedną linię kodu.

Ok, a jak tego użyć we właściwym activiti layoucie? Korzystamy z dyrektywy include, która może przyjmować parametry takie jakie dostarcza nam właściwy layout. Oto jakie efekty udało mi się osiągnąć:


A oto jak wygląda fragment layoutu:

    <include
        android:layout_width=”wrap_content”
        android:layout_height=”wrap_content”
        layout=”@layout/stack_bricks_layout”
        android:layout_above=”@+id/lineTV”
        android:layout_alignParentStart=”true”
        android:layout_marginLeft=”15dp”
        android:layout_marginRight=”15dp”
        android:layout_toStartOf=”@+id/ITV”
        android:layout_below=”@+id/plusLeftBtn”
        android:id=”@+id/includeLeftStack” />

    <include
        android:layout_width=”wrap_content”
        android:layout_height=”wrap_content”
        layout=”@layout/stack_bricks_layout”
        android:layout_above=”@+id/lineTV”
        android:layout_alignParentEnd=”true”
        android:layout_marginLeft=”15dp”
        android:layout_marginRight=”15dp”
        android:layout_toEndOf=”@+id/ITV”
        android:id=”@+id/includeRightStack”

        android:layout_below=”@+id/minusRightBtn” />

…

Jak widać, zawiera ona dwie dyrektywy include, które działają z tymi przyciskami do góry (plus i minus z prawej i lewej strony). I tutaj dochodzimy do sedna problemu. Mamy po dwa takie same identyfikatory na jednym activiti-layoucie.  Tzn np: @+id/brick1V po prawej i po lewej stronie, w jaki sposób przyciski odróżniają, do którego layoutu mają dodawać i odejmować? Działa to w ten sposób, że łączymy się z każdym z tych layoutów dzięki identyfikatorowi dyrektywy include. Znowu będę posiłkował się kodem:
…
    public View[] getBricksStack(View container)
    {
        View bricks1 = container.findViewById(R.id.brick1V);
…
        View bricks10 = container.findViewById(R.id.brick10V);
        return new View[]{null, bricks1, bricks2, bricks3, bricks4, bricks5, bricks6,
                bricks7, bricks8, bricks9, bricks10};
    }


    public void refreshView(int counter, View[] bricksArray)
    {
        for(int i = 1; i<=10; i++)
        {
            if(i<=counter)
                bricksArray[i].setVisibility(View.VISIBLE);
            else
                bricksArray[i].setVisibility(View.INVISIBLE);
        }
    }
…

…
    private View[] bricksLeftArray;
    private View[] bricksRightArray;
…
    StackBricksViewGroup stackBricksViewGroup = new StackBricksViewGroup();
        View stackBricksContainerLeft = findViewById(R.id.includeLeftStack);
        View stackBricksContainerRight = findViewById(R.id.includeRightStack);
        bricksLeftArray = stackBricksViewGroup.getBricksStack(stackBricksContainerLeft);
        bricksRightArray = stackBricksViewGroup.getBricksStack(stackBricksContainerRight);

Zastosowanie tego typu podejścia znacznie ogranicza potrzeby kopiowania kodu, możemy stworzyć wielopoziomowy layout i wykorzystywać go w naprawdę fajny sposób, dzięki czemu zmian będziemy dokonywali zawsze tylko w jednym miejscu.

Na ten moment zrezygnowałem z korzystania z fragmentów w aplikacji, w następnym wpisie spróbuję pokazać jak poprawić animację pojawiania się naszego obiektu, jeszcze nigdy tego nie robiłem 😉

PS. Pozwolę sobie na pochwalenie się statystykami od kiedy wystartowałem w tym konkursie 😉

Może w dobie internetu, gdzie zdjęcie z kotem zdobywa milion odsłon to nie dużo, nie mniej dla mnie, to aż 100 osób które interesowało to co mam zaoferowania. Nabrałem ochoty stworzyć post który będzie miał o wiele lepszą jakość, niż zwykła notka. Chcę to robić lepiej, ponieważ ktoś chce to czytać. Dzięki! 😉
Pozdrawiam!

#1 Pierwszy problem, jak zrozumieć liczby

Czym jest liczba? Korzystamy z nich na co dzień, ponieważ kiedyś nauczyliśmy się jak to robić, ale wyobraźmy sobie, że mamy przed sobą młody umysł. Umysł w którym ciężko się do czegoś odnieść, ponieważ nie ma zbyt wielu doświadczeń. W jaki sposób wytłumaczyć, czym jest liczba?

Po głębszym przemyśleniu chyba najlepiej będzie połączyć liczbę z ilością. Coś, co dziecko potrafi znaleźć w realnym świecie, jak 2 sztuki jabłka, czy 5 orzechów. W tej wersji spróbujmy po ilości „belek”, co zresztą sugeruje Montessori. Implementacja nie jest zbyt skomplikowana, dodajemy dwa przyciski, przy czym plus jest znacznie wyraźniejszy, bo chcemy zwrócić na niego uwagę. A do tego licznik ilości naszych belek, aby było maksymalnie 10. Po dojściu do maksymalnej liczby odblokuje się następne zadanie, jeszcze nie do końca wiem jak to będzie wyglądać 😉

Następnym krokiem będzie jakaś możliwość porównania ilości i dodania bądź odjęcia z każdej strony, aby nasza waga pozostała w równowadze. Mam nadzieję że będzie to zrozumiałe dla młodych osób 😉

Plany na najbliższym czas:
-dodanie fragmentów
-poprawienie wyglądu belek
-dodanie animacji dodawania poprzez zsuwanie z góry, odejmowania
-dodanie animacji plus/minus na dole
-następna aktywność: porównywanie

Projekt na github.

Pozdrawiam!