Android, kontrola stylów i sprawdzenie kodu.

Android, kontrola stylów i sprawdzenie kodu.
GregoryI
  • Rejestracja:ponad 9 lat
  • Ostatnio:około 18 godzin
  • Postów:238
1

Naszła mnie ochota, żeby zrobić poprawki do modułu aplikującego kolory ze zrobionych przeze mnie stylów do podanych elementów View. Jedna mała klasa która steruje całym procesem. Dzięki niej mam możliwość posiadania 3210941241 stylów w aplikacji i zmiany ich jednym kliknięciem ( ale dopiero po otworzeniu nowej aktywności, nie chce bawić mi się w dynamiczną implementację), oraz aplikacja kolorów ze stylów do obiektów które "nie są obsługiwane" automatycznie przez parametry stylów systemu Androida.

Czyli poprawianie wyglądu kodu tego gniota którego nie ruszałem od wielu miesięcy:).

Założenia które musiałem tu uwzględnić:

  1. Ma działać ze starymi wersjami Android API ( xxx < API version 21), czyt. ustawienie w plikach xml. koloru jako "?android:nazwa_atrybutu_ze_stylu" działa tylko dla kilku atrybutów; ma to działać np. na API version 16 oraz dla wszelkiego rodzaju dodanych własnoręcznie atrybutów;
  2. Kolory odczytywane są na podstawie podanego atrybutu (R.attr.nazwa_atrybutu) z bieżącego aktywnego stylu ( wszystkie style mają taki sam zestaw atrybutów, nie ma możliwości, że kolor odpowiadający atrybutowi nie zostanie odnaleziony);
  3. Kolor może być ustawiony dla typowego obiektu View ( kolor tła - background) lub dla View z wektorem jako tło (atrybut wektora "fillPath" - nie można ustawić koloru tego atrybutu bezpośrednio w java., ale można ustawić w xml. wektora DOMYŚLNY kolor biały i w java. DODAĆ na ten biały kolor filtr który da wygląd kolorowego obiektu);
  4. Kolory ustawione będą w obiekcie View jako tło ( nazwaView.setBackgroundColor(nazwaKoloru)) a dla View z vectorem jako colorFilter (nazwaView.getBackground.setColorFilter(nazwaKoloru));
  5. Obiekty będące polem klasy implementującej layout można przekazać bezpośrednio ( np. przekazać button mButton do metod wstawionych poniżej). Jeśli obiekt jest zagnieżdżony w innym View nie należy tworzyć nowych zmiennych za pomocą findViewById(...). Tak samo jeśli nie ma swojej reprezentacji jako pole w tamtej klasie ( np. ImageView ze zdefiniowanym obrazkiem, nie robimy pola w klasie MojaKlasa i findViewById(...)/ButterKnife bo nie ma po co). Ogólnie chodzi o to, że jeśli jest kilka obiektów tego samego typu w widoku, należy je wyciągnąć po typie ( oszczędzanie miejsca - przykład widok z 20 obiektami Button- dałby 20 linijek z wywołaniem metody, ale można iterować po wszystkich obiektach wchodzących w skład widoku, wyciągnąć obiekty danego typu (Button) i w ten sposób dodać referencję do nich do jakiejś listy).
  6. Istnieją następujące przypadki:
    a) mamy bezpośredni dostęp do obiektu dla którego chcemy ustawić kolor, jeden obiekt ( np. ustaw kolor dla tego obiektu ImageButton który jest polem w klasie implementującej layout).
    b) nie mamy bezpośredniego dostępu do obiektu dla którego chcemy ustawić kolor, jeden obiekt ( np. obiekt jest wstawiony w inny obiekt ( np. ListLayout z TextView i ImageView w środku i chcemy ustawić kolor dla ImageView)
    c) nie mamy bezpośredniego dostępu do obiektu dla którego chcemy ustawić kolor, więcej niż jeden obiekt ( np. Fragment mający w sobie 10 ImageView i dla nich ma być ustawiony kolor)
    d) wszystkie powyższe jeszcze raz, ale nie wiemy, czy mamy do czynienia z ustawieniem koloru tła czy dodaniem filtru koloru dla obiektu z wektorem;

Powyższe to wszystko co pamiętam na ten moment.
Podejmie się ktoś dyskusji co tu poprawić/połączyć/wywalić? :D

Klasa której kod chcę poprawić:

Kopiuj
public class StyleController {

    private Context mContext;

    public StyleController(Context context) {
        mContext = context;
    }

    public void applyColorAsBackground(View view, int attrId) {
        int colorId = getAttrColor(attrId);
        view.setBackgroundColor(colorId);
    }

    public void applyColorToSingleKnownView(View view, int attrId) {
        int colorId = getAttrColor(attrId);
        setColor(view, colorId);
    }

    public void applyColorToSingleUnknownNestedView(ViewGroup rootView, int attrId, Class<?> lookedType) {
        View view = pickSingleNestedView(rootView, lookedType);
        if (view != null) {
            applyColorToSingleKnownView(view, attrId);
        }
    }

    public void applyColorToMultipleViews(ViewGroup rootView, int attrId, Class<?> lookedType) {
        List<View> viewList = new ArrayList<>();
        pickMultipleNestedViews(rootView, viewList, lookedType);
        applyColorToView(viewList, attrId);
    }

    private void pickMultipleNestedViews(ViewGroup viewGroup, List<View> viewList, Class<?> lookedType) {
        for (int i=0; i< viewGroup.getChildCount(); i++) {
            if (viewGroup.getChildAt(i) instanceof ViewGroup) {
                pickMultipleNestedViews((ViewGroup) viewGroup.getChildAt(i), viewList, lookedType);
            } else if (lookedType.isInstance(viewGroup.getChildAt(i))){
                viewList.add(viewGroup.getChildAt(i));
            }
        }
    }

    private View pickSingleNestedView(ViewGroup viewGroup, Class<?> lookedType) {
        for (int i=0; i< viewGroup.getChildCount(); i++) {
            if (viewGroup.getChildAt(i) instanceof ViewGroup) {
                pickSingleNestedView((ViewGroup) viewGroup.getChildAt(i), lookedType);
            } else if (lookedType.isInstance(viewGroup.getChildAt(i))){
                return viewGroup.getChildAt(i);
            }
        }
        return null;
    }

    private void applyColorToView(List<View> viewList, int attrId) {
        int colorId = getAttrColor(attrId);
        for (View view : viewList) {
            setColor(view, colorId);
        }
    }

    public int getAttrColor(int attrId) {
        TypedValue typedValue = new TypedValue();
        // !!! set boolean resolveRes as true to make it work!!!
        mContext.getTheme().resolveAttribute(attrId, typedValue, true);
        return typedValue.data;
    }

    private void setColor(View view, int colorId) {
        if (isBackgroundInvisible(view)) {
            setBackgroundColor(view, colorId);
        } else {
            setColorFilter(view, colorId);
        }
    }

    private boolean isBackgroundInvisible(View view) {
        return view.getBackground() == null || !view.getBackground().isVisible();
    }

    private void setBackgroundColor(View view, int colorId) {
        view.setBackgroundColor(colorId);
    }

    private void setColorFilter(View view, int colorId) {
        view.getBackground().setColorFilter(colorId, PorterDuff.Mode.MULTIPLY);
    }

    public int getStyleAsInteger(String themeName) {
        int themeId;
        switch (themeName) {
            case "Light":
                themeId = R.style.Light;
                break;
            case "Dark":
                themeId = R.style.Dark;
                break;
            case "Candy":
                themeId = R.style.Candy;
                break;
            case "Dracula":
                themeId = R.style.Dracula;
                break;
            case "Ocean":
                themeId = R.style.Ocean;
                break;
            case "Forest":
                themeId = R.style.Forest;
                break;
            default:
                themeId = R.style.Light;
        }
        return themeId;
    }
}

I przykładowe użycie wygląda tak ( przyciski mają w sobie obrazek wektora, więc wymagane dla nich jest ustawienie filtru koloru a nie zmiana koloru tła:

Kopiuj
public class ExampleFragment extends BasicFragment {
 
    @BindView(R.id.reset_button) ImageButton mRefreshButton;
    @BindView(R.id.pause_button) ImageButton mPlayPauseButton;
    @BindView(R.id.lock_button) ImageButton mLockButton;
    @BindView(R.id.map_button) ImageButton mMapButton;
    @BindView(R.id.gps_button) ImageButton mGpsButton;
    @BindView(R.id.network_button) ImageButton mNetworkButton;
    @BindView(R.id.barometer_button) ImageButton mBarometerButton;

    private StyleController styleController;

    private void setButtonColorsByStyle() {
        ViewGroup viewContainer =  (ViewGroup) getView();
        Class<?> lookedType = ImageButton.class;
        applyColorToMultipleViews(viewContainer, R.attr.colorButtonPrimary, lookedType);
    }
}

Mi w oczy od razu rzuciły się:

  • możliwy null zwrócony (!!!) w metodzie "pickSingleNestedView"
  • nazewnictwo "applyColorToSingleKnownView" i "applyColorToSingleUnknownNestedView" - robiłem to, więc wiem =, że Known i Unknown miało oznaczać czy mamy dostęp do obiektu ( bezpośredni) i czy trzeba ustawić tam tło, cz filter, ale nie sądzę, żeby obca osoba łatwo to odczytała
  • możliwość połączenia metod "applyColorAsBackground", "applyColorToSingleKnownView" i "applyColorToSingleUnknownNestedView" - wszystkie ustawiają kolor dla jednego widoku, więc należałoby tutaj pokombinować...
  • metoda "getStyleAsInteger" długi switch

Jakiekolwiek uwagi mile widziane. Istotą wstawienia tego jest refactoring tego kodu do lepszej postaci, nie czy jest zasadny ( chociaż też uwagi przyjmę) :)

panryz
  • Rejestracja:prawie 17 lat
  • Ostatnio:11 minut
1

Prościej by było jakbyś zapodał kod na githuba z przykładową apką.
Co do refaktoringu.

  1. Próbowałeś do tego napisać jakiekolwiek testy?
  2. Skorzystaj może z annotacji, że metoda może rzucić nulla
  3. Ten switch to powinien przyjmować jakiegoś enuma, a nie string
GregoryI
  • Rejestracja:ponad 9 lat
  • Ostatnio:około 18 godzin
  • Postów:238
0
panryz napisał(a):

Prościej by było jakbyś zapodał kod na githuba z przykładową apką.
Co do refaktoringu.

  1. Próbowałeś do tego napisać jakiekolwiek testy?
  2. Skorzystaj może z annotacji, że metoda może rzucić nulla
  3. Ten switch to powinien przyjmować jakiegoś enuma, a nie string

Dzięki za podpowiedzi. Co do uwag:

  1. Nie, tak więc chyba powinienem:).

  2. Może jakoś uda mi się połączyć te wszystkie trzy metody o których wspomniałem i wywalić to miejsce.

  3. Co do ostatniego, tłumaczenie skąd pojawił się w ogóle switch ze string/int:
    Style mogę zmieniać/zmieniam z poziomu modułu który implementuje PreferenceScreen z androida.
    W Preferences mam listę która ma w sobie wszystkie możliwe do wyboru style ( w formie ListPreference a w array.xml listy z nagłówkami i wartościami).
    ListPreference obsługuje tablice z wartościami String, nie "int/Integer" ( stąd muszę operować na stringach z nazwą i później jakoś "przekształcić" tę nazwę na id stylu w formie "int" - tutaj wchodzi switch).
    Styl jest przypisywany do Activity w onCreate(), przed wywołaniem super.onCreate(). Tym sposobem jest przypisany do kontekstu i wszystkie fragmenty, widgety itp. które wchodzą w skład tej aktywności będą mieć ten styl ( z kontekstu).
    Przypisanie wygląda tak, że idę do SharedPreferences i odczytuję "parametr" który odpowiada zaznaczonemu polu z ListPreferences ze stylami.
    Tak jak pisałem wcześniej, zwrócony może być tylko String, więc muszę operować na nim.
    Jeśli da się to załatwić za pomocą enumów, to nie wiem o tym ( użyłem ich tylko raz w życiu, więc nie wiem za wiele o nich ^^).
    Idealnie byłoby gdybym mógł zwrócić poprzez SharedPreferences wartość stylu jako "int" ( czyli jego id) i po prostu bym je dalej przekazał, ale nie udało mi się znaleźć takiej możliwości.

Postaram się wstawić całą apkę tuta i dodam razem z linkiem do githuba.
PS. jakbym wstawiał tutaj apk., znasz może jakiś serwis żeby przepuścić jakimś "antywirusem"? Pamiętam, że odwiecznie ktoś narzeka, że "niewiadome źródło" i niesprawdzone/zawirusowane.

edytowany 4x, ostatnio: GregoryI
panryz
Napisz apkę przykładową i wstaw ją do projektu tak jak to jest w innych projektach np butterknife
GregoryI
  • Rejestracja:ponad 9 lat
  • Ostatnio:około 18 godzin
  • Postów:238
0

Podrzucam apk i linki.
plik apk.
xxx

powyższa klasa StyleController ( żeby nie szukać)
xxx

Od siebie dodam, że po intensywnym waleniu we wszystkie widgety, udało mi się zrobić crash aplikacji, ale nie mam czasu poprawić tego teraz. ( w jakimś przypadku musi być gdzieś błąd przy kliknięciu przycisku kłódki "lock" w module z diagramem).
Nie ustawiłem też dokładnie wszystkich kolorów w stylach ( no ale to do dopracowania, sama wizualizacja).
Najlepiej przeczytać readme.me ( które muszę zaktualizować, bo trochę tam rzeczy doszło już).
Tak więc miłego bawienia się:).

EDIT: A, zapomniałbym: włączenie usługi lokalizacji wymagane, bom leń i nie zrobiłem automatycznego żądania tego jeśli jest wyłączona.

EDIT po 2 dniach: zgodnie z przewidywaniami znikome zainteresowanie więc kasuję linki. Gdyby jakiś mod to przeczytał to temat do usunięcia.

edytowany 4x, ostatnio: GregoryI
Kliknij, aby dodać treść...

Pomoc 1.18.8

Typografia

Edytor obsługuje składnie Markdown, w której pojedynczy akcent *kursywa* oraz _kursywa_ to pochylenie. Z kolei podwójny akcent **pogrubienie** oraz __pogrubienie__ to pogrubienie. Dodanie znaczników ~~strike~~ to przekreślenie.

Możesz dodać formatowanie komendami , , oraz .

Ponieważ dekoracja podkreślenia jest przeznaczona na linki, markdown nie zawiera specjalnej składni dla podkreślenia. Dlatego by dodać podkreślenie, użyj <u>underline</u>.

Komendy formatujące reagują na skróty klawiszowe: Ctrl+B, Ctrl+I, Ctrl+U oraz Ctrl+S.

Linki

By dodać link w edytorze użyj komendy lub użyj składni [title](link). URL umieszczony w linku lub nawet URL umieszczony bezpośrednio w tekście będzie aktywny i klikalny.

Jeżeli chcesz, możesz samodzielnie dodać link: <a href="link">title</a>.

Wewnętrzne odnośniki

Możesz umieścić odnośnik do wewnętrznej podstrony, używając następującej składni: [[Delphi/Kompendium]] lub [[Delphi/Kompendium|kliknij, aby przejść do kompendium]]. Odnośniki mogą prowadzić do Forum 4programmers.net lub np. do Kompendium.

Wspomnienia użytkowników

By wspomnieć użytkownika forum, wpisz w formularzu znak @. Zobaczysz okienko samouzupełniające nazwy użytkowników. Samouzupełnienie dobierze odpowiedni format wspomnienia, zależnie od tego czy w nazwie użytkownika znajduje się spacja.

Znaczniki HTML

Dozwolone jest używanie niektórych znaczników HTML: <a>, <b>, <i>, <kbd>, <del>, <strong>, <dfn>, <pre>, <blockquote>, <hr/>, <sub>, <sup> oraz <img/>.

Skróty klawiszowe

Dodaj kombinację klawiszy komendą notacji klawiszy lub skrótem klawiszowym Alt+K.

Reprezentuj kombinacje klawiszowe używając taga <kbd>. Oddziel od siebie klawisze znakiem plus, np <kbd>Alt+Tab</kbd>.

Indeks górny oraz dolny

Przykład: wpisując H<sub>2</sub>O i m<sup>2</sup> otrzymasz: H2O i m2.

Składnia Tex

By precyzyjnie wyrazić działanie matematyczne, użyj składni Tex.

<tex>arcctg(x) = argtan(\frac{1}{x}) = arcsin(\frac{1}{\sqrt{1+x^2}})</tex>

Kod źródłowy

Krótkie fragmenty kodu

Wszelkie jednolinijkowe instrukcje języka programowania powinny być zawarte pomiędzy obróconymi apostrofami: `kod instrukcji` lub ``console.log(`string`);``.

Kod wielolinijkowy

Dodaj fragment kodu komendą . Fragmenty kodu zajmujące całą lub więcej linijek powinny być umieszczone w wielolinijkowym fragmencie kodu. Znaczniki ``` lub ~~~ umożliwiają kolorowanie różnych języków programowania. Możemy nadać nazwę języka programowania używając auto-uzupełnienia, kod został pokolorowany używając konkretnych ustawień kolorowania składni:

```javascript
document.write('Hello World');
```

Możesz zaznaczyć również już wklejony kod w edytorze, i użyć komendy  by zamienić go w kod. Użyj kombinacji Ctrl+`, by dodać fragment kodu bez oznaczników języka.

Tabelki

Dodaj przykładową tabelkę używając komendy . Przykładowa tabelka składa się z dwóch kolumn, nagłówka i jednego wiersza.

Wygeneruj tabelkę na podstawie szablonu. Oddziel komórki separatorem ; lub |, a następnie zaznacz szablonu.

nazwisko;dziedzina;odkrycie
Pitagoras;mathematics;Pythagorean Theorem
Albert Einstein;physics;General Relativity
Marie Curie, Pierre Curie;chemistry;Radium, Polonium

Użyj komendy by zamienić zaznaczony szablon na tabelkę Markdown.

Lista uporządkowana i nieuporządkowana

Możliwe jest tworzenie listy numerowanych oraz wypunktowanych. Wystarczy, że pierwszym znakiem linii będzie * lub - dla listy nieuporządkowanej oraz 1. dla listy uporządkowanej.

Użyj komendy by dodać listę uporządkowaną.

1. Lista numerowana
2. Lista numerowana

Użyj komendy by dodać listę nieuporządkowaną.

* Lista wypunktowana
* Lista wypunktowana
** Lista wypunktowana (drugi poziom)

Składnia Markdown

Edytor obsługuje składnię Markdown, która składa się ze znaków specjalnych. Dostępne komendy, jak formatowanie , dodanie tabelki lub fragmentu kodu są w pewnym sensie świadome otaczającej jej składni, i postarają się unikać uszkodzenia jej.

Dla przykładu, używając tylko dostępnych komend, nie możemy dodać formatowania pogrubienia do kodu wielolinijkowego, albo dodać listy do tabelki - mogłoby to doprowadzić do uszkodzenia składni.

W pewnych odosobnionych przypadkach brak nowej linii przed elementami markdown również mógłby uszkodzić składnie, dlatego edytor dodaje brakujące nowe linie. Dla przykładu, dodanie formatowania pochylenia zaraz po tabelce, mogłoby zostać błędne zinterpretowane, więc edytor doda oddzielającą nową linię pomiędzy tabelką, a pochyleniem.

Skróty klawiszowe

Skróty formatujące, kiedy w edytorze znajduje się pojedynczy kursor, wstawiają sformatowany tekst przykładowy. Jeśli w edytorze znajduje się zaznaczenie (słowo, linijka, paragraf), wtedy zaznaczenie zostaje sformatowane.

  • Ctrl+B - dodaj pogrubienie lub pogrub zaznaczenie
  • Ctrl+I - dodaj pochylenie lub pochyl zaznaczenie
  • Ctrl+U - dodaj podkreślenie lub podkreśl zaznaczenie
  • Ctrl+S - dodaj przekreślenie lub przekreśl zaznaczenie

Notacja Klawiszy

  • Alt+K - dodaj notację klawiszy

Fragment kodu bez oznacznika

  • Alt+C - dodaj pusty fragment kodu

Skróty operujące na kodzie i linijkach:

  • Alt+L - zaznaczenie całej linii
  • Alt+, Alt+ - przeniesienie linijki w której znajduje się kursor w górę/dół.
  • Tab/⌘+] - dodaj wcięcie (wcięcie w prawo)
  • Shit+Tab/⌘+[ - usunięcie wcięcia (wycięcie w lewo)

Dodawanie postów:

  • Ctrl+Enter - dodaj post
  • ⌘+Enter - dodaj post (MacOS)