alexvasilkov / GestureViews

ImageView and FrameLayout with gestures control and position animation
Apache License 2.0
2.37k stars 384 forks source link

surfaceView can't implement gestureView #110

Closed ZhouKanZ closed 6 years ago

ZhouKanZ commented 6 years ago

how can i make surfaceView have the same function?

     I have write a MapView with surfaceView ,then i wish it can be operated as well as GestureImageView,But it can't work. I hope to get your suggestion ,the following is my code:

package com.jms.cleanse.widget;

import android.annotation.SuppressLint; import android.content.Context; import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Path; import android.graphics.PixelFormat; import android.graphics.RectF; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.v4.content.ContextCompat; import android.util.AttributeSet; import android.util.Log; import android.view.MotionEvent; import android.view.SurfaceHolder; import android.view.SurfaceView;

import com.alexvasilkov.gestures.GestureController; import com.alexvasilkov.gestures.State; import com.alexvasilkov.gestures.views.interfaces.GestureView; import com.jms.cleanse.widget.mapview.POIConfig; import com.jms.cleanse.widget.mapview.ScaleUtils; import com.jms.cleanse.widget.mapview.TestPOI;

import java.util.ArrayList; import java.util.LinkedList; import java.util.List;

/**

public class JMMapView extends SurfaceView implements SurfaceHolder.Callback, Runnable,GestureView {

private static final String TAG = "JMMapView";
private final GestureController controller;

private Paint mPaint;
private Canvas canvas;

private SurfaceHolder holder;
private Thread t;
private boolean isDrawing = false;
private List<TestPOI> testPOIS;
private LinkedList<Path> paths;
private LinkedList<RectF> points;

/************ 地图坐标系相关的配置 *************/
private int coodinateX;
private int coodinateY;
private Bitmap cleanseRes;
private Bitmap unCleanseRes;
private Bitmap pathStartRes;
private Bitmap pathEndRes;
private int colorCleanse;
private int colorUnCleanse;
// 地图
private Bitmap map;

private Resources resources;

/* 事件 */
private OnClickListener onClickListener;

@Override
public GestureController getController() {
    return controller;
}

public interface OnClickListener {
    void onPointClick(TestPOI poi, int pos);

    void onPathClick(int pos);
}

public void setOnClickListener(OnClickListener onClickListener) {
    this.onClickListener = onClickListener;
}

public JMMapView(@NonNull Context context) {
    this(context, null);
}

public JMMapView(@NonNull Context context, @Nullable AttributeSet attrs) {
    this(context, attrs, 0);
}

@SuppressLint("ResourceType")
public JMMapView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);

    this.setFocusable(true);
    this.setZOrderOnTop(true);

    holder = getHolder();
    holder.addCallback(this);
    holder.setFormat(PixelFormat.TRANSPARENT);

// this.setSurfaceTextureListener(this);

    mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
    mPaint.setColor(Color.BLUE);
    mPaint.setStrokeCap(Paint.Cap.ROUND);
    mPaint.setStrokeWidth(5);
    mPaint.setStyle(Paint.Style.STROKE);

    testPOIS = new ArrayList<>();
    paths = new LinkedList<>();
    points = new LinkedList<>();

    resources = getResources();
    cleanseRes = BitmapFactory.decodeStream(resources.openRawResource(POIConfig.cleanseRes));
    unCleanseRes = BitmapFactory.decodeStream(resources.openRawResource(POIConfig.unCleanseRes));
    pathStartRes = BitmapFactory.decodeStream(resources.openRawResource(POIConfig.pathStartRes));
    pathEndRes = BitmapFactory.decodeStream(resources.openRawResource(POIConfig.pathEndRes));

    colorCleanse = ContextCompat.getColor(this.getContext(), POIConfig.pathColorCleanse);
    colorUnCleanse = ContextCompat.getColor(this.getContext(), POIConfig.pathColorUnCleanse);

    /* make jmmapview support gesture */
    controller = new GestureController(this);
    controller.getSettings().setOverzoomFactor(1f).setPanEnabled(false);
    controller.getSettings().initFromAttributes(context, attrs);
    controller.getSettings().setZoomEnabled(true);
    controller.getSettings().setRotationEnabled(true);
    controller.addOnStateChangeListener(new GestureController.OnStateChangeListener() {
        @Override
        public void onStateChanged(State state) {
            applyState(state);
        }

        @Override
        public void onStateReset(State oldState, State newState) {
            applyState(newState);
        }
    });

}

/**
 *
 * @param state
 */
private void applyState(State state) {

}

@Override
public void surfaceCreated(SurfaceHolder holder) {
    isDrawing = true;
    t = new Thread(this);
    t.start();
}

@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
}

@Override
public void surfaceDestroyed(SurfaceHolder holder) {
    isDrawing = false;
}

@Override
public void run() {
    if (isDrawing) {
        drawMap();
    }
}

/**
 * 绘制地图
 */
private void drawMap() {

    try {
        if (null != holder) {
            canvas = holder.lockCanvas();
            canvas.save();
            if (!canvasChange()) {
                return;
            }
            canvas.drawColor(Color.WHITE);

            drawBitMap();
            drawPath();
            drawPoi();
            canvas.restore();
        }
    } catch (Exception e) {
        e.getStackTrace();
    } finally {

        if (null != canvas) {
            holder.unlockCanvasAndPost(canvas);
        }
    }
}

/**
 * @aim: 进行画布操作,将绘制起点设置为bitmap的左上角,坐标系转换到bitmap的像素坐标系上,方便作图
 */
private boolean canvasChange() {
    if (map != null) {
        // 缩放倍数
        float ratio = ScaleUtils.getScaleRatio(ScaleUtils.ScaleType.CENTER_INSIDE, map, getWidth(), getHeight());
        // 宽的差值
        float dw = getWidth() - map.getWidth() * ratio;
        // 高的差值
        float dh = getHeight() - map.getHeight() * ratio;
        canvas.scale(ratio, ratio);
        // 横屏
        if (getWidth() > getHeight()) {
            canvas.translate(dw / (2 * ratio), 0);
            // 竖屏
        } else {
            canvas.translate(0, dh / (2 * ratio));
        }
        // 计算坐标系的最大值
        coodinateX = map.getWidth();
        coodinateY = map.getHeight();
        Log.d(TAG, "canvasChange: " + coodinateX + ",y" + coodinateY);
    } else {
        return false;
    }
    return true;
}

/**
 * 绘制地图,在绘制之前需要确认bitmap缩放的方式并返回得到的bitmap.
 */
private void drawBitMap() {
    if (map != null) {
        canvas.drawBitmap(map, 0, 0, mPaint);
    }
}

/**
 * 绘制POI点 测试点数据 (-2.5,4.4)
 */
private void drawPoi() {
    TestPOI lastPoi = null;
    for (int i = 0; i < testPOIS.size(); i++) {
        TestPOI poiPoint = testPOIS.get(i);
        if (poiPoint.isCleanse()) {
            canvas.drawBitmap(cleanseRes, null, points.get(i), mPaint);
        } else if (lastPoi != null && lastPoi.isCleanse()) {
            canvas.drawBitmap(cleanseRes, null, points.get(i), mPaint);
        } else {
            canvas.drawBitmap(unCleanseRes, null, points.get(i), mPaint);
        }
        lastPoi = poiPoint;

        /* 绘制起点和终点 */
        if (i == 0) {

// canvas.drawBitmap(pathStartRes,null,getBitmapRectF(poiPoint.getAx(),poiPoint.getAy(),pathStartRes),mPaint); canvas.drawBitmap(pathStartRes, (float) (poiPoint.getAx() - pathStartRes.getWidth() / 2), (float) (poiPoint.getAy() - pathStartRes.getHeight()), mPaint); // canvas.drawRect(getBitmapRectF(poiPoint.getAx(),poiPoint.getAy(),pathEndRes),mPaint); } else if (i > 0 && i == testPOIS.size() - 1) { // canvas.drawBitmap(pathStartRes,null,getBitmapRectF(poiPoint.getAx(),poiPoint.getAy(),pathStartRes),mPaint); // mPaint.setColor(Color.BLUE); // canvas.drawRect(getBitmapRectF(poiPoint.getAx(),poiPoint.getAy(),pathEndRes),mPaint); canvas.drawBitmap(pathEndRes, (float) (poiPoint.getAx() - pathEndRes.getWidth() / 2), (float) (poiPoint.getAy() - pathEndRes.getHeight()), mPaint); } }

}

/**
 * 获取Bitmap矩形区域
 *
 * @param ax
 * @param ay
 * @param bm
 */
private RectF getBitmapRectF(double ax, double ay, Bitmap bm) {

    int w = bm.getWidth();
    int h = bm.getHeight();

    RectF rectF = new RectF();
    rectF.left = (float) (ax - w / 2);
    rectF.top = (float) (ay - h);
    rectF.right = (float) (ay + w / 2);
    rectF.bottom = (float) ay;

    return rectF;
}

@SuppressLint("ResourceType")
private void drawPath() {

    getPathList(this.testPOIS);
    for (int i = 0; i < testPOIS.size(); i++) {
        TestPOI poiPoint = testPOIS.get(i);
        // 最后一个点 直接跳出循环
        if (i == testPOIS.size() - 1) {
            break;
        }
        if (poiPoint.isCleanse()) {
            mPaint.setColor(colorCleanse);
        } else {
            mPaint.setColor(colorUnCleanse);
        }
        canvas.drawPath(paths.get(i), mPaint);
    }

}

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    int w = 0;
    int h = 0;
    if (null != map) {
        w = map.getWidth();
        h = map.getHeight();
    }
    setMeasuredDimension(getDefaultSize(w, widthMeasureSpec), getDefaultSize(h, heightMeasureSpec));
}

/**
 * 设置地图
 */
public void setMap(Bitmap map) {
    this.map = map;
    invalidate();
}

public void setTestPOIS(List<TestPOI> testPOIS) {
    if (testPOIS != null && testPOIS.size() > 0) {
        this.testPOIS.clear();
        this.testPOIS.addAll(testPOIS);
        invalidate();
    }
}

@Override
public boolean onTouchEvent(MotionEvent event) {

    float x = event.getX();
    float y = event.getY();

    switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN:
            // 点被点击
            Log.d(TAG, "onTouchEvent: 我被点了" + x+",y"+y);
            for (int i = 0; i <points.size() ; i++) {
                if (onClickListener!=null){
                    if (points.get(i).contains(x,y)){
                        onClickListener.onPointClick(testPOIS.get(i),i);
                        Log.d(TAG, "onTouchEvent: " + i+"我被点了");
                    }
                }
            }
            break;
        case MotionEvent.ACTION_UP:
            break;
        case MotionEvent.ACTION_CANCEL:
            break;
        case MotionEvent.ACTION_MOVE:
            break;
    }

    return controller.onTouch(this,event);
}

/**
 * 根据poi点得到对应的路径组合
 *
 * @param testPOIS
 */
private void getPathList(List<TestPOI> testPOIS) {

    // 记录上一个点的坐标
    double[] lastPoi = null;
    for (int i = 0; i < testPOIS.size(); i++) {
        TestPOI poiPoint = testPOIS.get(i);
        double[] currentPoi = poiPoint.getAndroidCoordinate(coodinateX, coodinateY);
        Log.d(TAG, "getPathList: x:" + currentPoi[0] + ",y:" + currentPoi[1]);
        if (lastPoi != null) {
            Path p = new Path();
            p.moveTo((float) lastPoi[0], (float) lastPoi[1]);
            p.lineTo((float) currentPoi[0], (float) currentPoi[1]);
            // 添加路径
            paths.add(p);
        }
        lastPoi = currentPoi;
        // 添加矩形区域
        points.add(poiPoint.getRect(currentPoi[0], currentPoi[1], coodinateX, coodinateY, cleanseRes.getWidth() / 2));

    }
}

}

alexvasilkov commented 6 years ago

Too much code :) What are you trying to achieve and what is not working for you?

alexvasilkov commented 6 years ago

I see you are using standalone GestureController instance, so basically you are implementing custom view so please check wiki on how to deal with it.

Also your applyState() method is empty, you need to apply current state to you map somehow, otherwise it definitely won't work. Not sure what you want to achieve though, so can't really suggest anything.

ZhouKanZ commented 6 years ago

:) I thought I didn't need to operate matrix,I will try it again .

ZhouKanZ commented 6 years ago

The MapView is just like ImageView with scaleType = center_inside; and draw POI and path on the Bitmap of map;I operated on the canvas in order to implement this feature (center_inside);I almost finished, but there are some problems:

the key code as following: image image image

I hope these can be expressed clearly !

The first question is: rotation center is (0,0) at upper left corner, however I want the bitmap to rotate around the physical center.

The second is: I tried to set diablebound, but the image can still be zoomed to extreme detail and swapped out of screen, too free. I want to restrict the user operation on the image.

The last question is: I want to listen the click event of poi (point of interest) on the bitmap, and because of matrix transformation, I don't know how to match user click point and the exact original point on map

alexvasilkov commented 6 years ago

Few notes:

Note, that if you'll use GestureFrameLayout and regular Android views as your pins then you can just listen for those views' on-click events.

ZhouKanZ commented 6 years ago

thanks ! follow u suggestion i finished this view . hah

ZhouKanZ commented 6 years ago

I regret to close this issue , Now have another question , Click Event have been triggered many times,When clicked once .

 @Override
 public boolean onTouch(View v, MotionEvent event) {

    float[] point = new float[2];
    point[0] = event.getX();
    point[1] = event.getY();
    index = -1;
    Log.d(TAG, "onTouchEvent: origin point :x" + point[0] + ",y:" + point[1]);
    changeMatrix.invert(matrix);
    matrix.mapPoints(point, new float[]{point[0], point[1]});
    Log.d(TAG, "onTouchEvent: transfrom point:x" + point[0] + ",y:" + point[1]);
    switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN:
            startClickTime = Calendar.getInstance().getTimeInMillis();
            break;
        case MotionEvent.ACTION_UP:
            clickEvent(point);
            break;
        case MotionEvent.ACTION_CANCEL:
            break;
        case MotionEvent.ACTION_MOVE:
            break;
    }
    return controller.onTouch(this, event);
}

   private void clickEvent(float[] point) {
    long clickDuration = Calendar.getInstance().getTimeInMillis() - startClickTime;
    if (clickDuration < MAX_CLICK_DURATION && onClickListener != null) {
        Observable.fromIterable(points)
                .filter(rectF -> rectF.contains(point[0], point[1]))
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(rectF -> {
                            Log.d(TAG, "onTouchEvent: xxxxx");
                            onClickListener.onPointClick(testPOIS.get(0), index);
                        }
                        , t -> {
                        }
                );
    }

}

private void applyState(State state) {
    state.get(changeMatrix);
    invalidate();
}
alexvasilkov commented 6 years ago

Your click implementation is not implemented correctly, simply waiting for ACTION_UP will not work.

You should try using OnGestureListener as described in usage wiki.

ZhouKanZ commented 6 years ago

thank u ! Harassing for a long time .

alexvasilkov commented 6 years ago

@ZhouKanZ Is this issue solved for you?

ZhouKanZ commented 6 years ago

it had been solved,tks !