Search code examples
androidonlongclicklistenersubsampling

Adding Marker Pins at touched Position using subsampling scale image view


I'm using "subsampling scale image view" in my app and I would like to dynamically add marker pins to a PinView when the user does a long click on it. The marker pin should appear at the clicked position.

I achieved that a marker pin appears after a long click, but at a wrong position. Here is the onCreateView method of my Fragment:

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
                         Bundle savedInstanceState) {
    pinCounter=0;

    View rootView = inflater.inflate(R.layout.fragment_edit_plan, container, false);
    src = getArguments().getString("src");
    imageView = (PinView)rootView.findViewById(R.id.imageView);

    imageView.setImage(ImageSource.uri(src));
    imageView.isLongClickable();
    imageView.isClickable();
    imageView.hasOnClickListeners();

    MapPins = new ArrayList();
    imageView.setPins(MapPins);

    imageView.setOnLongClickListener(this);
    imageView.setOnTouchListener(this);

    return  rootView;
}

The Listener-Methods:

@Override
public boolean onLongClick(View view) {
    if (view.getId() == R.id.imageView) {
        Toast.makeText(getActivity(), "Long clicked "+lastKnownX+" "+lastKnownY, Toast.LENGTH_SHORT).show();
        Log.d("EditPlanFragment","Scale "+imageView.getScale());
        MapPins.add(new MapPin(lastKnownX,lastKnownY,pinCounter));
        imageView.setPins(MapPins);

        imageView.post(new Runnable(){
            public void run(){
                imageView.getRootView().postInvalidate();
            }
        });

        return true;
    }
    return false;
}

@Override
public boolean onTouch(View v, MotionEvent event) {
    if (v.getId()== R.id.imageView && event.getAction() == MotionEvent.ACTION_DOWN){
        lastKnownX= event.getX();
        lastKnownY= event.getY();
    }
    return false;
}

My XML-File:

<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/content"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingTop="50dp"
>

<com.example.viktoriabock.phoenixversion1.PinView
    android:id="@+id/imageView"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:longClickable="true"

   />

And the PinView-Class:

public class PinView extends SubsamplingScaleImageView {

    private PointF sPin;

    ArrayList<MapPin> mapPins;
    ArrayList<DrawPin> drawnPins;
    Context context;
    String tag = getClass().getSimpleName();

    public PinView(Context context) {
        this(context, null);
        this.context = context;
    }

    public PinView(Context context, AttributeSet attr) {
        super(context, attr);
        this.context = context;
        initialise();
    }

    public void setPins(ArrayList<MapPin> mapPins) {
        this.mapPins = mapPins;
        initialise();
        invalidate();
    }

    public void setPin(PointF pin) {
        this.sPin = pin;
    }

    public PointF getPin() {
        return sPin;
    }

    private void initialise() {

    }

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

        // Don't draw pin before image is ready so it doesn't move around during       setup.
        if (!isReady()) {
            return;
        }

        drawnPins = new ArrayList<>();

        Paint paint = new Paint();
        paint.setAntiAlias(true);
        float density = getResources().getDisplayMetrics().densityDpi;


        for (int i = 0; i < mapPins.size(); i++) {
            MapPin mPin = mapPins.get(i);
            //Bitmap bmpPin = Utils.getBitmapFromAsset(context, mPin.getPinImgSrc());
            Bitmap bmpPin = BitmapFactory.decodeResource(this.getResources(), R.drawable.pushpin_blue);

            float w = (density / 420f) * bmpPin.getWidth();
            float h = (density / 420f) * bmpPin.getHeight();
            bmpPin = Bitmap.createScaledBitmap(bmpPin, (int) w, (int) h, true);

            PointF vPin = sourceToViewCoord(mPin.getPoint());
            //in my case value of point are at center point of pin image, so we need to adjust it here

            float vX = vPin.x - (bmpPin.getWidth() / 2);
            float vY = vPin.y - bmpPin.getHeight();

            canvas.drawBitmap(bmpPin, vX, vY, paint);

            //add added pin to an Array list to get touched pin
            DrawPin dPin = new DrawPin();
            dPin.setStartX(mPin.getX() - w / 2);
            dPin.setEndX(mPin.getX() + w / 2);
            dPin.setStartY(mPin.getY() - h / 2);
            dPin.setEndY(mPin.getY() + h / 2);
            dPin.setId(mPin.getId());
            drawnPins.add(dPin);
        }
    }

    public int getPinIdByPoint(PointF point) {
        for (int i = drawnPins.size() - 1; i >= 0; i--) {
            DrawPin dPin = drawnPins.get(i);
            if (point.x >= dPin.getStartX() && point.x <= dPin.getEndX()) {
                if (point.y >= dPin.getStartY() && point.y <= dPin.getEndY()) {
                    return dPin.getId();
                }
            }
        }
        return -1; //negative no means no pin selected
    }
}

Solution

  • You're collecting the screen coordinates of the long press, then in the custom view you're converting them from source coordinates to screen coordinates. You need to convert the event coordinates into source coordinates when you get the tap event, using the view's viewToSourceCoord method.

    There's an easier way to do this than using your lastKnown values - use your own GestureDetector.

    For a full working example of a long press gesture detector, see the AdvancedEventHandlingActivity sample class.