Jak znaleźć absolutną pozycję kliknięcia podczas powiększania

Proszę zobaczyć każdą sekcję poniżej, aby opisać mój problem opisany na trzy różne sposoby. Mam nadzieję, że pomoże ludziom odpowiedzieć.

Problem: Jak znaleźć parę współrzędnych wyrażonych w obszarze canvas / userspace, gdy masz tylko współrzędną wyrażoną w postaci powiększonego obrazu, biorąc pod uwagę oryginalny punkt skali i współczynnik skali?

Problem w praktyce:

Obecnie próbuję odtworzyć funkcjonalność zoomu używaną w aplikacje takie jak Galeria / mapy, kiedy można uszczypnąć, aby powiększyć/pomniejszyć z powiększeniem poruszającym się w kierunku środka uszczypnięcia.

Na dole zapisuję środkowy punkt zoomu (który jest we współrzędnych X,Y na podstawie bieżącego ekranu). Po wykryciu gestu "skali" mam tę funkcję:

class ImageScaleGestureDetector extends SimpleOnScaleGestureListener {
    @Override
    public boolean onScale(ScaleGestureDetector detector) {
        if(mScaleAllowed) 
            mCustomImageView.scale(detector.getScaleFactor(), mCenterX, mCenterY);
        return true;
    }
}   

Funkcja skali CustomImageView wygląda tak:

public boolean scale(float scaleFactor, float focusX, float focusY) {
    mScaleFactor *= scaleFactor;

    // Don't let the object get too small or too large.
    mScaleFactor = Math.max(MINIMUM_SCALE_VALUE, Math.min(mScaleFactor, 5.0f));

    mCenterScaleX = focusX;
    mCenterScaleY = focusY;

    invalidate();

    return true;
}

Rysowanie zeskalowanego obrazu uzyskuje się poprzez nadpisanie metody onDraw, która skaluje płótno wokół środka i rysuje obraz do niego.

@Override
public void onDraw(Canvas canvas) {
    super.onDraw(canvas);

    canvas.save();
    canvas.translate(mCenterScaleX, mCenterScaleY);
    canvas.scale(mScaleFactor, mScaleFactor);
    canvas.translate(-mCenterScaleX, -mCenterScaleY);
    mIcon.draw(canvas);
    canvas.restore();
}

To wszystko działa dobrze podczas skalowania z ScaleFactor 1, to dlatego, że początkowe mcenterx i mCenterY są współrzędne, które są oparte na ekranie urządzenia. 10, 10 na urządzeniu to 10, 10 na płótnie.

Gdy jednak już powiększysz, następnym razem, gdy klikniesz pozycję 10, 10, nie będzie ona już odpowiadać pozycji 10, 10 w obszarze roboczym ze względu na skalowanie i przekształcanie, które już zostało wystąpili

Problem w abstrakcji:

Poniższy obrazek jest przykładem operacji powiększenia wokół punktu środkowego A. każde pole reprezentuje pozycję i rozmiar widoku, gdy przy tym współczynniku skali(1, 2, 3, 4, 5).

Przykład

W przykładzie, jeśli przeskalowano o współczynnik 2 wokół A, a następnie kliknięto pozycję B, X, Y zgłaszane jako B będą oparte na pozycji ekranu, a nie na pozycji względem 0,0 początkowego obszaru roboczego.

I need to znajdź sposób na uzyskanie bezwzględnej pozycji B.

Author: Graeme, 2011-11-18

3 answers

Więc po przerysowaniu problemu znalazłem rozwiązanie, którego szukałem. To już przeszło przez kilka iteracji, ale oto jak to wypracowałem:

Opis Rozwiązania

B-Punkt, środek działania skali

A1, A2, A3-punkty, równe w przestrzeni użytkownika, ale różne w przestrzeni płótna.

Znasz wartości dla Bx i By ponieważ są one zawsze stałe bez względu na współczynnik skali (znasz tę wartość zarówno w canvas-space, jak i w przestrzeń użytkownika).

Wiesz Ax & Ay w przestrzeni użytkownika, dzięki czemu można znaleźć odległość między Ax do Bx i Ay do By. Ten pomiar znajduje się w przestrzeni użytkownika, aby przekształcić go w pomiar przestrzeni płótna, po prostu podziel go przez współczynnik skali. (Po przekonwertowaniu na obszar roboczy możesz zobaczyć te linie w kolorze czerwonym, pomarańczowym i żółtym).

Ponieważ punkt B jest stały, odległość między nim a krawędziami jest stała (są one reprezentowane przez niebieskie linie). Wartość ta jest równa w przestrzeń użytkownika i przestrzeń płótna.

Znasz szerokość płótna w przestrzeni płótna, więc odejmując te dwa wymiary przestrzeni płótna (Ax do Bx i Bx do Edge) od całkowitej szerokości pozostajesz ze współrzędnymi punktu a w przestrzeni płótna:

public float[] getAbsolutePosition(float Ax, float Ay) {
    
    float fromAxToBxInCanvasSpace = (mCenterScaleX - Ax) / mScaleFactor;
    float fromBxToCanvasEdge = mCanvasWidth - Bx;
    float x = mCanvasWidth - fromAxToBxInCanvasSpace - fromBxToCanvasEdge;
    
    float fromAyToByInCanvasSpace = (mCenterScaleY - Ay) / mScaleFactor;
    float fromByToCanvasEdge = mCanvasHeight - By;
    float y = mCanvasHeight - fromAyToByInCanvasSpace - fromByToCanvasEdge;
    
    return new float[] { x, y };
}

Powyższy kod i obrazek opisują po kliknięciu w lewym górnym rogu oryginalnego środka. Użyłem tej samej logiki, aby znaleźć bez względu na to, w którym kwadrancie się znajduje, i refakturowałem do następującego:

public float[] getAbsolutePosition(float Ax, float Ay) {

    float x = getAbsolutePosition(mBx, Ax);
    float y = getAbsolutePosition(mBy, Ay); 

    return new float[] { x, y };
}

private float getAbsolutePosition(float oldCenter, float newCenter, float mScaleFactor) {
    if(newCenter > oldCenter) {
        return oldCenter + ((newCenter - oldCenter) / mScaleFactor);
    } else {
        return oldCenter - ((oldCenter - newCenter) / mScaleFactor);
    }
}
 14
Author: Graeme,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-06-20 09:12:55

Oto moje rozwiązanie oparte na odpowiedzi Graeme ' a:

public float[] getAbsolutePosition(float Ax, float Ay) {
    MatrixContext.drawMatrix.getValues(mMatrixValues);

    float x = mWidth - ((mMatrixValues[Matrix.MTRANS_X] - Ax) / mMatrixValues[Matrix.MSCALE_X])
            - (mWidth - getTranslationX());

    float y = mHeight - ((mMatrixValues[Matrix.MTRANS_Y] - Ay) / mMatrixValues[Matrix.MSCALE_X])
            - (mHeight - getTranslationY());

    return new float[] { x, y };
}

Parametry AX i Ay są punktami, które użytkownik dotyka za pomocą onTouch (), posiadałem statyczną instancję macierzy w klasie MatrixContext do przechowywania poprzednich skalowanych / przetłumaczonych wartości.

 2
Author: Keanu Pang,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-04-30 08:05:10

Naprawdę przepraszam, że to krótka odpowiedź, w pośpiechu. Ale ostatnio też na to patrzyłem - znalazłem http://code.google.com/p/android-multitouch-controller / robić to, co chcesz (chyba-musiałem przeczytać twój post). Mam nadzieję, że to pomoże. Przyjrzę się temu dzisiaj, jeśli to nie pomoże i zobaczę, czy mogę pomóc dalej.

 1
Author: Martyn,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-11-18 09:56:35