Pytanie Rozszerzanie RelativeLayout i przesłonięcie metody dispatchDraw () w celu utworzenia powiększalnej grupy ViewGroup


Widziałem, jak kilka osób pytało, jak powiększyć całą grupę ViewGroup (taką jak RelativeLayout) za jednym razem. W tej chwili jest to coś, co muszę osiągnąć. Najbardziej oczywistym podejściem byłoby dla mnie utrzymywanie współczynnika skali powiększenia jako pojedynczej zmiennej; W każdym z widoków podrzędnych "onDraw ()", ten współczynnik skali zostanie zastosowany do obszaru roboczego przed narysowaniem grafiki.

Jednak zanim to zrobiłem, starałem się być sprytny (ha - zazwyczaj zły pomysł) i rozszerzyć RelativeLayout na klasę o nazwie ZoomableRelativeLayout. Moim pomysłem jest, że każda transformacja skali może być zastosowana tylko raz do funkcji Canvas w zastąpionej funkcji dispatchDraw (), tak że nie byłoby absolutnie żadnej potrzeby osobnego zastosowania zoomu w żadnym z widoków podrzędnych.

Oto co zrobiłem w moim ZoomableRelativeLayout. Jest to tylko proste rozszerzenie RelativeLayout, przy czym dispatchDraw () jest nadpisywane:

protected void dispatchDraw(Canvas canvas){         
    canvas.save(Canvas.MATRIX_SAVE_FLAG);       
    canvas.scale(mScaleFactor, mScaleFactor);
    super.dispatchDraw(canvas);     
    canvas.restore();       
}

MScaleFactor jest manipulowany przez ScaleListener w tej samej klasie.

To faktycznie działa. Mogę uszczypnąć, aby powiększyć ZoomableRelativeLayout, a wszystkie widoki utrzymane w należytym przeskalowaniu razem.

Tylko że jest problem. Niektóre z tych widoków podrzędnych są animowane, a zatem okresowo wywołuję na nich unieważniające (). Kiedy skala wynosi 1, te widoki podrzędne są odświeżane okresowo idealnie dokładnie. Gdy skala jest inna niż 1, animowane widoki podrzędne są wyświetlane tylko w części obszaru podglądu - lub wcale - w zależności od skali zoomu.

Moje początkowe myślenie było takie, że kiedy wywoływany jest widok unieważniający () dla pojedynczego podrzędnego dziecka, to może być przerysowany indywidualnie przez system, a nie przekazywany jako płótno z dispativeDraw () rodzica RelativeLayout, co oznacza, że ​​widok potomny odświeża się sam bez zastosowanej skali zoomu. Dziwne jednak, elementy widoków podrzędnych, które są przerysowywane na ekranie, mają poprawną skalę zoomu. To prawie tak, jakby obszar, który system decyduje się na aktualizację w bitmapie, pozostaje nieskalowany - jeśli ma to sens. Innymi słowy, jeśli mam jedno animowane dziecko Zobacz i stopniowo powiększam obraz dalej i dalej od początkowej skali 1, i jeśli umieszczamy wyimaginowane pole na obszarze, w którym ten widok dziecka jest, gdy skala zoomu wynosi 1 , wtedy wywołania invalidate () powodują tylko odświeżenie grafiki w tym wyimaginowanym oknie. Ale grafika, którą można zaktualizować  wykonywane w odpowiedniej skali. Jeśli powiększysz widok tak bardzo, że widok podrzędny całkowicie się odsunął od skali 1, żadna jego część nie zostanie odświeżona. Podam inny przykład: wyobraź sobie, że mój widok dziecka to piłka, która ożywia się, przełączając się między żółtym a czerwonym. Jeśli powiększę trochę tak, że piłka przesunie się w prawo iw dół, w pewnym momencie zobaczysz tylko lewą górną ćwiartkę kuli ożywiającej kolory.

Jeśli ciągle powiększam i pomniejszam, widzę animację widoków dziecka prawidłowo i całkowicie. Wynika to z przerysowania całej grupy ViewGroup.

Mam nadzieję, że to ma sens; Starałem się wyjaśnić najlepiej jak potrafię. Czy jestem nieco przegrany z moją możliwą do powiększenia strategią ViewGroup? Czy istnieje inny sposób?

Dzięki,

Trev


24
2018-06-16 21:32


pochodzenie


Po wielu lekturach mam teraz wrażenie, że ma to związek z obszarem wyświetlania / "obszarem nieprawidłowym" / obszarem klipu, który początkowo określono dla widoku. Próbowałem użyć android: clipChildren = "false", ale to nie miało żadnego efektu. - Trevor
Czy próbowałeś sprawdzać widoki podrzędne w przeglądarce hierarchii po powiększeniu? - matheeeny
Aby potwierdzić swoją teorię unieważnionego obszaru, spróbuj zainstalować aplikację "Dev Tools" (com.android.development, powinna już być zainstalowana w emulatorze), otwórz aplikację i przejdź do "Development Settings"> zaznacz "Show screen updates" . To spowoduje, że wszelkie aktualizacje ekranu spowodowane przez unieważnione obszary zostaną podświetlone w kolorze magenta. Jeśli chodzi o Twój przypadek użycia, myślę, że animowanie wyświetleń byłoby bardzo dziwne, podczas gdy jest skalowane w jakikolwiek sposób. Spróbuj przesłonić canAnimate () i zwraca false, jeśli jest powiększony? - Joe
Joe i matheeeny: Wielkie dzięki za te sugestie; to jest bardzo cenne. W międzyczasie dojdę do rozwiązania roboczego (które przedstawiłem poniżej jako odpowiedź), ale teraz będę miał do dyspozycji narzędzia programistyczne, o których wspomniałeś, ponieważ będzie to warte zachodu. - Trevor


Odpowiedzi:


Jeśli stosujesz współczynnik skalowania do rysowania swoich dzieci, musisz również zastosować odpowiedni współczynnik skali do wszystkich innych interakcji z nimi - wysyłając zdarzenia dotyku, unieważnia itd.

Tak więc oprócz dispatchDraw (), będziesz musiał przesłonić i odpowiednio dostosować zachowanie co najmniej tych innych metod. Aby zająć się unieważnieniami, musisz zastąpić tę metodę, aby odpowiednio dostosować współrzędne dziecka:

http://developer.android.com/reference/android/view/ViewGroup.html#invalidateChildInParent (int [], android.graphics.Rect)

Jeśli chcesz, aby użytkownik mógł wchodzić w interakcję z widokami podrzędnymi, musisz również zastąpić to, aby odpowiednio dostosować współrzędne dotyku, zanim zostaną one wysłane do dzieci:

http://developer.android.com/reference/android/view/ViewGroup.html#dispatchTouchEvent(android.view.MotionEvent)

Również zdecydowanie zaleciłbym wdrożyć to wszystko wewnątrz prostej podklasy ViewGroup, która ma pojedynczy widok podrzędny, którym zarządza. Pozwoli to pozbyć się złożoności zachowań wprowadzanych przez RelativeLayout we własnej grupie ViewGroup, upraszczając to, co jest potrzebne, i debugując w swoim własnym kodzie. Umieść RelativeLayout jako dziecko swojej specjalnej powiększającej ViewGroup.

W końcu, jedno ulepszenie do twojego kodu - w dispatchDraw (), który chcesz zapisać stan płótna po zastosowanie współczynnika skalowania. Dzięki temu dziecko nie będzie mogło modyfikować transformacji, którą ustawiłeś.


14
2018-06-27 08:53



Hackbod: Kiedy będziesz online. Potrzebuję pomocy, jeśli możesz pomóc ... możemy gdzieś pogadać? Z góry dziękuję - user1201239
nie powinno ustawićScaleX / Y zrobić to dla każdego z dzieci w grupie widoku? - numan salati


Doskonała odpowiedź od hackbod przypomniała mi, że muszę opublikować rozwiązanie, do którego ostatecznie doszedłem. Należy zauważyć, że to rozwiązanie, które pracowało dla mnie w związku z aplikacją, którą wówczas robiłem, można jeszcze ulepszyć dzięki sugestiom hackbod. W szczególności nie musiałem obsługiwać zdarzeń dotykowych i dopóki nie przeczytałem postu hackbod, nie przyszło mi do głowy, że gdybym to zrobił, również musiałbym je skalować.

Podsumowując, dla mojego wniosku, co musiałem osiągnąć, to mieć duży diagram (konkretnie układ piętra budynku) z innymi małymi symbolami "znacznika" nałożonymi na niego. Schemat tła i symbole pierwszego planu są rysowane za pomocą grafiki wektorowej (to znaczy obiektów Path () i Paint () zastosowanych do Canvas w metodzie onDraw ()). Powodem, dla którego chce się tworzyć w ten sposób całą grafikę, a nie tylko używanie zasobów bitmapowych, jest to, że grafiki są konwertowane w czasie wykonywania za pomocą mojego konwertera obrazów SVG.

Wymagało to, aby diagram i związane z nim symbole znaczników były potomkami grupy ViewGroup i mogły być razem zbliżone do siebie.

Wiele kodu wygląda na niechlujną (to była pilna praca dla demonstracji), więc zamiast po prostu skopiować to wszystko, zamiast tego spróbuję po prostu wyjaśnić, jak to zrobiłem z odpowiednimi fragmentami kodu.

Przede wszystkim mam ZoomableRelativeLayout.

public class ZoomableRelativeLayout extends RelativeLayout { ...

Ta klasa obejmuje klasy nasłuchiwania, które rozszerzają ScaleGestureDetector i SimpleGestureListener, dzięki czemu układ można przesuwać i powiększać. Szczególnie interesujący jest tu detektor gestów skalowania, który ustawia zmienną współczynnika skalowania, a następnie wywołuje metody invalidate () i requestLayout (). W tej chwili nie jestem całkowicie pewien, czy unieważnienie () jest konieczne, ale w każdym razie - tutaj jest:

private class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener {

@Override
public boolean onScale(ScaleGestureDetector detector){
    mScaleFactor *= detector.getScaleFactor();
    // Apply limits to the zoom scale factor:
    mScaleFactor = Math.max(0.6f, Math.min(mScaleFactor, 1.5f);
    invalidate();
    requestLayout();
    return true;
    }
}

Następną rzeczą, którą musiałem zrobić w moim ZoomableRelativeLayout było przesłonięcie onLayout (). Aby to zrobić, uważam, że warto przyjrzeć się próbom innych ludzi w układzie z możliwością powiększania, a także bardzo przydatnym jest sprawdzenie oryginalnego kodu źródłowego Androida dla RelativeLayout. Moja nadpisana metoda kopiuje wiele z tego, co znajduje się w opcji OnLayout () ReliefLayout, ale z pewnymi modyfikacjami.

@Override
protected void onLayout(boolean changed, int l, int t, int r, int b)
{
    int count = getChildCount();
    for(int i=0;i<count;i++){
        View child = getChildAt(i); 
        if(child.getVisibility()!=GONE){
            RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams)child.getLayoutParams();
            child.layout(
                (int)(params.leftMargin * mScaleFactor), 
                (int)(params.topMargin * mScaleFactor), 
                (int)((params.leftMargin + child.getMeasuredWidth()) * mScaleFactor), 
                (int)((params.topMargin + child.getMeasuredHeight()) * mScaleFactor) 
                );
        }
    }
}

Istotne jest to, że podczas wywoływania "layout ()" na wszystkich dzieciach, stosuję współczynnik skalowania również do parametrów układu dla tych dzieci. Jest to jeden z kroków do rozwiązania problemu z obcinaniem, a także, co ważne, poprawnie ustawia pozycję x, y dzieci względem siebie dla różnych współczynników skali.

Kolejną ważną rzeczą jest to, że nie próbuję już skalować płótna w dispatchDraw (). Zamiast tego każdy widok podrzędny skaluje swoje płótno po uzyskaniu współczynnika skalowania od rodzica ZoomableRelativeLayout za pomocą metody gettera.

Następnie przejdę do tego, co musiałem zrobić w obrębie podrzędnych widoków mojego ZoomableRelativeLayout. W moim ZoomableRelativeLayout istnieje tylko jeden typ widoku, który zawieram jako dzieci; to widok do rysowania grafiki SVG, którą nazywam SVGView. Oczywiście rzeczy SVG nie mają tu znaczenia. Oto jego metoda onMeasure ():

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {


    int widthMode = MeasureSpec.getMode(widthMeasureSpec);
    int widthSize = MeasureSpec.getSize(widthMeasureSpec);

    int heightMode = MeasureSpec.getMode(heightMeasureSpec);
    int heightSize = MeasureSpec.getSize(heightMeasureSpec);

    float parentScale = ((FloorPlanLayout)getParent()).getScaleFactor();
    int chosenWidth, chosenHeight;
    if( parentScale > 1.0f){
        chosenWidth =  (int) ( parentScale * (float)svgImage.getDocumentWidth() );
        chosenHeight = (int) ( parentScale * (float)svgImage.getDocumentHeight() );
    }
    else{
        chosenWidth =  (int) (  (float)svgImage.getDocumentWidth() );
        chosenHeight = (int) (  (float)svgImage.getDocumentHeight() );          
    }

    setMeasuredDimension(chosenWidth, chosenHeight);
}

I onDraw ():

@Override
protected void onDraw(Canvas canvas){   
    canvas.save(Canvas.MATRIX_SAVE_FLAG);       

    canvas.scale(((FloorPlanLayout)getParent()).getScaleFactor(), 
            ((FloorPlanLayout)getParent()).getScaleFactor());       


    if( null==bm  ||  bm.isRecycled() ){
        bm = Bitmap.createBitmap(
                getMeasuredWidth(),
                getMeasuredHeight(),
                Bitmap.Config.ARGB_8888);


        ... Canvas draw operations go here ...
    }       

    Paint drawPaint = new Paint();
    drawPaint.setAntiAlias(true);
    drawPaint.setFilterBitmap(true);

    // Check again that bm isn't null, because sometimes we seem to get
    // android.graphics.Canvas.throwIfRecycled exception sometimes even though bitmap should
    // have been drawn above. I'm guessing at the moment that this *might* happen when zooming into
    // the house layout quite far and the system decides not to draw anything to the bitmap because
    // a particular child View is out of viewing / clipping bounds - not sure. 
    if( bm != null){
        canvas.drawBitmap(bm, 0f, 0f, drawPaint );
    }

    canvas.restore();

}

Znowu - jako zastrzeżenie, są prawdopodobnie pewne brodawki w tym, co tam zamieściłem, a ja muszę jeszcze ostrożnie przejść przez sugestie hackbod i włączyć je. Zamierzam wrócić i edytować to dalej. W międzyczasie mam nadzieję, że zacznie dostarczać użytecznych wskazówek innym, jak wdrożyć powiększalną grupę ViewGroup.


14
2018-06-28 18:24



co to jest FloorPlanLayout? czy możesz podać cały kod lub przykład, kiedy używam twojego kodu, ale nie działa, mimo że wyjaśniłeś bardzo szczegółowo. - pengwang
Co to jest układ planu piętra? Ja też nie zrozumiałem. Zrobiłem niestandardowy RelativeLayout, czytając na twoim linku. Mój niestandardowy układ względny zachowuje się jak układ nadrzędny dla moich 7 widoków graficznych. Teraz napotkane problemy? 1. Jeśli utrzymuję obraz jako tło względnego widoku i próbuję go powiększyć, to nie uzyskuję powiększenia, ale jeśli zrobię to jako widok obrazu i umieści go jako dziecko niestandardowego wskaźnika względnego, to będzie on prawidłowo powiększał obraz z wielu widoków w środku, w przypadku niestandardowego układu względnego nie powiększa ustawionego tła? - user1201239
+1 Wyjaśnij, co to jest FloorPanLayout - edoardotognoni
Dla każdego, kto natknie się na to (lub zostanie tutaj połączony), FloorPlanLayout jest nieistotny. Jest to mniej więcej pojemnik widoku root, w którym początkowo ustawiona jest skala, a widok jest udokumentowany jako widok pośredni, który przechowuje zawartość. - Abandoned Cart