Android - Как получить границы границ на масштабируемом макете с функцией панорамирования / перетаскивания? - PullRequest
0 голосов
/ 28 августа 2018

Я сейчас занимаюсь разработкой игры, в которой мне нужна своего рода карта "overworld" с кликабельными зданиями и прочим материалом на нем.

Карта должна быть похожа на картинку в галерее:

  • увеличение до определенного масштабного коэффициента с помощью двойного касания (масштабирование также может быть нормальным, но это не обязательно)
  • уменьшить до коэффициента масштабирования 1,0 с помощью двойного нажатия (после увеличения)
  • при увеличении карта должна поддерживать горизонтальное и вертикальное панорамирование / перетаскивание
  • также он должен иметь своего рода «привязанное» -обнаружение , поэтому карта не может быть вытащена за пределы экрана.

Проблема в том, что все здания и другие вещи должны быть добавлены в это представление. Поэтому, когда родительский вид (карта) увеличивается или перемещается, здания должны перемещаться и увеличиваться с родительским (карта).

Я искал решения и нашел несколько сообщений вроде:

Просмотр с горизонтальным и вертикальным панорамированием / перетаскиванием и пинч-зумом

Как получить функцию масштабирования для изображений?

Основываясь на этих постах, я попробовал следующий код для достижения своей "карты"

public class ZoomableViewGroup extends ViewGroup{

// these matrices will be used to move and zoom image
private Matrix matrix = new Matrix();
private Matrix matrixInverse = new Matrix();
private Matrix savedMatrix = new Matrix();
// we can be in one of these 3 states
private static final int NONE = 0;
private static final int DRAG = 1;
private static final int ZOOM = 2;
private int mode = NONE;
// remember some things for zooming
private PointF start = new PointF();
private PointF mid = new PointF();
private float oldDist = 1f;
private float[] lastEvent = null;

float MAX_ZOOM = 1.8f;
float MIN_ZOOM = 1f;

private boolean initZoomApplied=false;

private float[] mDispatchTouchEventWorkingArray = new float[2];
private float[] mOnTouchEventWorkingArray = new float[2];

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
    mDispatchTouchEventWorkingArray[0] = ev.getX();
    mDispatchTouchEventWorkingArray[1] = ev.getY();
    mDispatchTouchEventWorkingArray = screenPointsToScaledPoints(mDispatchTouchEventWorkingArray);
    ev.setLocation(mDispatchTouchEventWorkingArray[0],
            mDispatchTouchEventWorkingArray[1]);
    return super.dispatchTouchEvent(ev);
}

private float[] scaledPointsToScreenPoints(float[] a) {
    matrix.mapPoints(a);
    return a;
}

private float[] screenPointsToScaledPoints(float[] a){
    matrixInverse.mapPoints(a);
    return a;
}

public ZoomableViewGroup(Context context) {
    super(context);
    init(context);
}

public ZoomableViewGroup(Context context, AttributeSet attrs) {
    super(context, attrs);
    init(context);
}

public ZoomableViewGroup(Context context, AttributeSet attrs,
                         int defStyleAttr) {
    super(context, attrs, defStyleAttr);
    init(context);
}

/**
 * Determine the space between the first two fingers
 */
private float spacing(MotionEvent event) {
    float x = event.getX(0) - event.getX(1);
    float y = event.getY(0) - event.getY(1);
    return (float)Math.sqrt(x * x + y * y);
}

/**
 * Calculate the mid point of the first two fingers
 */
private void midPoint(PointF point, MotionEvent event) {
    float x = event.getX(0) + event.getX(1);
    float y = event.getY(0) + event.getY(1);
    point.set(x / 2, y / 2);
}


private void init(Context context){

}


@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
    int childCount = getChildCount();
    for (int i = 0; i < childCount; i++) {
        View child = getChildAt(i);
        if (child.getVisibility() != GONE) {
            child.layout(child.getLeft(), child.getTop(), child.getLeft() + child.getMeasuredWidth(), child.getTop() + child.getMeasuredHeight());
        }
    }
}


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

    int widthSize = MeasureSpec.getSize(widthMeasureSpec);
    int heightSize = MeasureSpec.getSize(heightMeasureSpec);

    float[] values = new float[9];
    matrix.getValues(values);
    float container_width = values[Matrix.MSCALE_X]*widthSize;
    float container_height = values[Matrix.MSCALE_Y]*heightSize;

    //Log.d("zoomToFit", "m width: "+container_width+" m height: "+container_height);
    //Log.d("zoomToFit", "m x: "+pan_x+" m y: "+pan_y);

    int childCount = getChildCount();
    for (int i = 0; i < childCount; i++) {
        View child = getChildAt(i);
        if (child.getVisibility() != GONE) {
            measureChild(child, widthMeasureSpec, heightMeasureSpec);

            if(i==0 && !initZoomApplied && child.getWidth()>0){
                int c_w = child.getWidth();
                int c_h = child.getHeight();

                zoomToFit(c_w, c_h, container_width, container_height);
            }
        }
    }

}

private void zoomToFit(int c_w, int c_h, float container_width, float container_height){
    float proportion_firstChild = (float)c_w/(float)c_h;
    float proportion_container = container_width/container_height;

    Log.d("zoomToFit", "firstChildW: "+c_w+" firstChildH: "+c_h);
    //Log.d("zoomToFit", "proportion-container: "+proportion_container);
    //Log.d("zoomToFit", "proportion_firstChild: "+proportion_firstChild);

    if(proportion_container<proportion_firstChild){
        float initZoom = container_height/c_h;
        //Log.d("zoomToFit", "adjust height with initZoom: "+initZoom);
        matrix.postScale(initZoom, initZoom);
        matrix.postTranslate(-1*(c_w*initZoom-container_width)/2, 0);
        matrix.invert(matrixInverse);
    }else {
        float initZoom = container_width/c_w;
        //Log.d("zoomToFit", "adjust width with initZoom: "+initZoom);
        matrix.postScale(initZoom, initZoom);
        matrix.postTranslate(0, -1*(c_h*initZoom-container_height)/2);
        matrix.invert(matrixInverse);
    }
    initZoomApplied=true;
    invalidate();
}

@Override
protected void dispatchDraw(Canvas canvas) {
    canvas.save();
    canvas.setMatrix(matrix);
    super.dispatchDraw(canvas);
    canvas.restore();
}

@Override
public boolean onTouchEvent(MotionEvent event) {
    // handle touch events here
    mOnTouchEventWorkingArray[0] = event.getX();
    mOnTouchEventWorkingArray[1] = event.getY();

    mOnTouchEventWorkingArray = scaledPointsToScreenPoints(mOnTouchEventWorkingArray);

    event.setLocation(mOnTouchEventWorkingArray[0], mOnTouchEventWorkingArray[1]);

    switch (event.getAction() & MotionEvent.ACTION_MASK) {
        case MotionEvent.ACTION_DOWN:
            savedMatrix.set(matrix);
            start.set(event.getX(), event.getY());
            mode = DRAG;
            lastEvent = null;
            break;
        case MotionEvent.ACTION_POINTER_DOWN:
            oldDist = spacing(event);
            if (oldDist > 10f) {
                savedMatrix.set(matrix);
                midPoint(mid, event);
                mode = ZOOM;
            }
            lastEvent = new float[4];
            lastEvent[0] = event.getX(0);
            lastEvent[1] = event.getX(1);
            lastEvent[2] = event.getY(0);
            lastEvent[3] = event.getY(1);
            //d = rotation(event);
            break;
        case MotionEvent.ACTION_UP:
        case MotionEvent.ACTION_POINTER_UP:
            mode = NONE;
            lastEvent = null;
            break;
        case MotionEvent.ACTION_MOVE:

            if (mode == DRAG) {

                View child = getChildAt(0);
                    child.layout(child.getLeft(), child.getTop(), child.getLeft() + child.getMeasuredWidth(), child.getTop() + child.getMeasuredHeight());

                matrix.set(savedMatrix);
                float dx = event.getX() - start.x;
                float dy = event.getY() - start.y;

                matrix.postTranslate(dx, dy);
                matrix.invert(matrixInverse);

            } else if (mode == ZOOM) {
                float newDist = spacing(event);
                if (newDist > 10f) {
                    matrix.set(savedMatrix);
                    float scale = (newDist / oldDist);
                    float[] values = new float[9];
                    matrix.getValues(values);

                    if(scale*values[Matrix.MSCALE_X] >= MAX_ZOOM){
                        scale = MAX_ZOOM/values[Matrix.MSCALE_X];
                    }
                    if(scale*values[Matrix.MSCALE_X] <= MIN_ZOOM){
                        scale = MIN_ZOOM/values[Matrix.MSCALE_X];
                    }

                    matrix.postScale(scale, scale, mid.x, mid.y);
                    matrix.invert(matrixInverse);
                }
            }
            break;
    }

    invalidate();
    return true;
}

Затем я добавил ImageView (фоновый рисунок карты) и кнопку тестирования. (представитель для зданий и прочего) на ZoomableViewGroup.

Результат с кодом выше: карта (рис.), которую можно увеличивать и уменьшать при помощи пинч-зума (масштабный коэффициент 1,0 - 1,8). Он также может перетаскиваться по горизонтали и вертикали. Кнопка также масштабируется и перетаскивается.

Но чего не хватает, так это проверки границ экрана или / и / по отношению к ImageView. Так что на самом деле можно перетащить карту за пределы экрана. или увидеть белый фон.

Как мне выполнить проверку / обнаружение привязки?

Возможно ли это с кодом выше? (Если нет, что я должен использовать вместо этого?)

...