android-如何使用平移/拖曳功能在可缩放布局上获取边界限制?

时间:2018-08-28 18:04:20

标签: android android-layout draggable pinchzoom

我目前正在开发一款游戏,在该游戏中,我需要一种“世界”地图 上面有可点击的建筑物和其他东西。

地图应该像画廊中的图片一样显示

    通过轻敲将
  • 放大到定义的比例因子(捏缩放也可以,但这不是必须的)
  • 缩小,通过点按(放大后)缩放比例系数1.0
  • 放大后,地图应支持水平和垂直平移/拖动
  • 它还应该具有一种“绑定”-检测,因此地图不能拖出屏幕。

问题是,所有建筑物和其他东西都需要添加到此视图中。 因此,在放大或移动父视图(地图)时,建筑物需要与父视图一起移动并缩放(地图)。

我搜索了解决方案,发现了一些帖子,例如:

View with horizontal and vertical pan/drag and pinch-zoom

How can I get zoom functionality for images?

基于这些帖子,我尝试了以下代码来实现“地图”

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的边界。因此实际上可以将地图拖出屏幕 或看到白色背景。

我如何实现绑定检查/检测?

上面的代码甚至可能吗? (如果没有,我应该怎么用?)

0 个答案:

没有答案