I'm using Dave Morrissey's Subsampling Scale Image View. I'm using his Pinview example (as shown here: https://github.com/davemorrissey/subsampling-scale-image-view/blob/master/sample/src/com/davemorrissey/labs/subscaleview/sample/extension/views/PinView.java)
What I've done differently is that I've created an ArrayList of Bitmaps that are Pins. But I want to make each pin clickable to set off an on click function. I know Bitmaps can not be clicked on. I have multiple pins on a map image and would like for each pin to be associated with an object.
What would be the best approach to accomplish this?
Note: I did override the setOnClickListener method inside of the Pinview class, but what happens that all pins that were dropped become associated to the same object. And that clearing 1 pin would then clear all pins.
The model that stores the bitmap, pointF and point name:
public class CategoryPoint {
private String category;
private Bitmap image;
private PointF pointF;
public CategoryPoint(String category, Bitmap image, PointF pointF) {
this.category = category;
this.image = image;
this.pointF = pointF;
}
// getters/setters
}
View looks like this:
public class PinsView extends SubsamplingScaleImageView {
private OnPinClickListener onPinClickListener;
private final Paint paint = new Paint();
private List<CategoryPoint> categoryPoints;
public PinsView(Context context) {
this(context, null);
}
public PinsView(Context context, AttributeSet attr) {
super(context, attr);
categoryPoints = new ArrayList<>();
initTouchListener();
}
public void addCategories(List<CategoryPoint> categoryPoints) {
this.categoryPoints = categoryPoints;
invalidate();
}
public void removeCategories(List<CategoryPoint> categoryPoints) {
this.categoryPoints.removeAll(categoryPoints);
invalidate();
}
public void removeAllCategories() {
this.categoryPoints.clear();
invalidate();
}
public void setOnPinClickListener(OnPinClickListener listener) {
onPinClickListener = listener;
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (!isReady()) {
return;
}
paint.setAntiAlias(true);
for (CategoryPoint categoryPoint: categoryPoints) {
Bitmap pinIcon = categoryPoint.getImage();
if (categoryPoint.getPointF() != null && categoryPoint.getImage() != null) {
PointF point = sourceToViewCoord(categoryPoint.getPointF());
float vX = point.x - (pinIcon.getWidth()/2);
float vY = point.y - pinIcon.getHeight();
canvas.drawBitmap(pinIcon, vX, vY, paint);
}
}
}
private void initTouchListener() {
GestureDetector gestureDetector = new GestureDetector(getContext(), new GestureDetector.SimpleOnGestureListener() {
#Override
public boolean onSingleTapConfirmed(MotionEvent e) {
if (isReady() && categoryPoints != null) {
PointF tappedCoordinate = new PointF(e.getX(), e.getY());
Bitmap clickArea = categoryPoints.get(0).getImage();
int clickAreaWidth = clickArea.getWidth();
int clickAreaHeight = clickArea.getHeight();
for (CategoryPoint categoryPoint : categoryPoints) {
PointF categoryCoordinate = sourceToViewCoord(categoryPoint.getPointF());
int categoryX = (int) (categoryCoordinate.x);
int categoryY = (int) (categoryCoordinate.y - clickAreaHeight / 2);
if (tappedCoordinate.x >= categoryX - clickAreaWidth / 2
&& tappedCoordinate.x <= categoryX + clickAreaWidth / 2
&& tappedCoordinate.y >= categoryY - clickAreaHeight / 2
&& tappedCoordinate.y <= categoryY + clickAreaHeight / 2) {
onPinClickListener.onPinClick(categoryPoint);
break;
}
}
}
return true;
}
});
setOnTouchListener((v, event) -> gestureDetector.onTouchEvent(event));
}
}
Fragment:
pinView.setOnImageEventListener(this);
pinView.setOnPinClickListener(this);
// implementation
Related
I am using google maps on my project. I want to put arrow to screen if marker not showing on the screen. How can I control screen?
Actually, the answer for your question is in lakshman.pasala comment, but its implementation is a little bit complex (TLDR).
So, best way to get Google Maps Screen Control - is implement custom view which extends MapView class. In that case you can get full control of drawing on view canvas. To do that you should override dispatchDraw() method (because MapView extends FrameLayout which is ViewGroup) and implement within it arrow drawing. Something like that:
#Override
public void dispatchDraw(Canvas canvas) {
super.dispatchDraw(canvas);
canvas.save();
drawArrowToMarker(canvas);
canvas.restore();
}
and you need to call it via invalidate() on every map move/zoom/rotate. For detect map move/zoom/rotate you need GoogleMap (exactly GoogleMap.setOnCameraMoveListener() method). You can declare GoogleMap object inside your custom MapView class and set it via setter, but better if custom MapView class will implement OnMapReadyCallback interface and get it in onMapReady() callback. Also your need several utility methods for adding/removing marker, determine segments intersections, directions etc.
Full source code of custom view (e.g. EnhanchedMapView) can be like:
public class EnhanchedMapView extends MapView implements OnMapReadyCallback {
private final static int ARROW_PADDING = 50;
private final static double ARROW_ANGLE = Math.PI / 6;
private final static double ARROW_LENGTH = 100;
private final static double ARROW_SIZE = 50;
private OnMapReadyCallback mMapReadyCallback;
private GoogleMap mGoogleMap;
private Marker mMarker;
private Paint mPaintArrow;
public EnhanchedMapView(#NonNull Context context) {
super(context);
init();
}
public EnhanchedMapView(#NonNull Context context, #Nullable AttributeSet attrs) {
super(context, attrs);
init();
}
public EnhanchedMapView(#NonNull Context context, #Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
public EnhanchedMapView(#NonNull Context context, #Nullable GoogleMapOptions options) {
super(context, options);
init();
}
#Override
public void dispatchDraw(Canvas canvas) {
super.dispatchDraw(canvas);
canvas.save();
drawArrowToMarker(canvas);
canvas.restore();
}
private void drawArrowToMarker(Canvas canvas) {
if (mGoogleMap == null || mMarker == null) {
return;
}
VisibleRegion visibleRegion = mGoogleMap.getProjection().getVisibleRegion();
LatLngBounds screenBounds = visibleRegion.latLngBounds;
LatLng mapCenter = screenBounds.getCenter();
Projection mapProjection = mGoogleMap.getProjection();
final Point pointMapCenter = mGoogleMap.getProjection().toScreenLocation(mapCenter);
final Point pointTopLeft = mapProjection.toScreenLocation(visibleRegion.farLeft);
final Point pointTopRight = mapProjection.toScreenLocation(visibleRegion.farRight);
final Point pointBottomLeft = mapProjection.toScreenLocation(visibleRegion.nearLeft);
final Point pointBottomRight = mapProjection.toScreenLocation(visibleRegion.nearRight);
final Point pointMarker = mapProjection.toScreenLocation(mMarker.getPosition());
final Point tl = new Point(pointTopLeft.x + ARROW_PADDING, pointTopLeft.y + ARROW_PADDING);
final Point tr = new Point(pointTopRight.x - ARROW_PADDING, pointTopRight.y + ARROW_PADDING);
final Point br = new Point(pointBottomRight.x - ARROW_PADDING, pointBottomRight.y - ARROW_PADDING);
final Point bl = new Point(pointBottomLeft.x + ARROW_PADDING, pointBottomLeft.y - ARROW_PADDING);
final Point pointIntersection = getBoundsIntersection(tl, tr, br, bl, pointMapCenter, pointMarker);
if (pointIntersection != null) {
double angle = Math.atan2(pointMarker.y - pointMapCenter.y, pointMarker.x - pointMapCenter.x);
int arrowX, arrowY;
arrowX = (int) (pointIntersection.x - ARROW_LENGTH * Math.cos(angle));
arrowY = (int) (pointIntersection.y - ARROW_LENGTH * Math.sin(angle));
canvas.drawLine(pointIntersection.x, pointIntersection.y, arrowX, arrowY, mPaintArrow);
arrowX = (int) (pointIntersection.x - ARROW_SIZE * Math.cos(angle + ARROW_ANGLE));
arrowY = (int) (pointIntersection.y - ARROW_SIZE * Math.sin(angle + ARROW_ANGLE));
canvas.drawLine(pointIntersection.x, pointIntersection.y, arrowX, arrowY, mPaintArrow);
arrowX = (int) (pointIntersection.x - ARROW_SIZE * Math.cos(angle - ARROW_ANGLE));
arrowY = (int) (pointIntersection.y - ARROW_SIZE * Math.sin(angle - ARROW_ANGLE));
canvas.drawLine(pointIntersection.x, pointIntersection.y, arrowX, arrowY, mPaintArrow);
}
}
private void init() {
setWillNotDraw(false);
mPaintArrow = new Paint();
mPaintArrow.setColor(Color.BLUE);
mPaintArrow.setStrokeWidth(15);
}
#Override
public void getMapAsync(OnMapReadyCallback callback) {
mMapReadyCallback = callback;
super.getMapAsync(this);
}
#Override
public void onMapReady(GoogleMap googleMap) {
mGoogleMap = googleMap;
mGoogleMap.setOnCameraMoveListener(new GoogleMap.OnCameraMoveListener() {
#Override
public void onCameraMove() {
invalidate();
}
});
if (mMapReadyCallback != null) {
mMapReadyCallback.onMapReady(googleMap);
}
}
public void addMarker(MarkerOptions markerOptions) {
removeMarker();
mMarker = mGoogleMap.addMarker(markerOptions);
}
public void removeMarker() {
mGoogleMap.clear();
}
private static boolean floatEquals(float f1, float f2) {
final double EPS = 1e-6;
return (Math.abs(f1 - f2) < EPS);
}
private static Point getBoundIntersection(Point p11, Point p12, Point p21, Point p22) {
double x, y;
Point intersectionPoint = null;
// test intersection with vertical bound
if (floatEquals(p12.x, p11.x) || floatEquals(p22.x, p21.x)) {
if (floatEquals(p12.x, p11.x) && floatEquals(p22.x, p21.x) && !floatEquals(p11.x, p21.x)) {
return null;
} else {
if (floatEquals(p12.x, p11.x)) {
x = p11.x;
y = (x - p21.x) / (p22.x - p21.x) * (p22.y - p21.y) + p21.y;
if (x >= Math.min(p21.x, p22.x) && x <= Math.max(p21.x, p22.x)
&& y >= Math.min(p11.y, p12.y) && y <= Math.max(p11.y, p12.y)) {
intersectionPoint = new Point((int) x, (int) y);
}
} else {
x = p21.x;
y = (x - p11.x) / (p12.x - p11.x) * (p12.y - p11.y) + p11.y;
if (x >= Math.min(p11.x, p12.x) && x <= Math.max(p11.x, p12.x)
&& y >= Math.min(p21.y, p22.y) && y <= Math.max(p21.y, p22.y)) {
intersectionPoint = new Point((int) x, (int) y);
}
}
}
} else {
// test intersection with horizontal bound
if (floatEquals(p12.y, p11.y) || floatEquals(p22.y, p21.y)) {
if (floatEquals(p12.y, p11.y) && floatEquals(p22.y, p21.y) && !floatEquals(p11.y, p21.y)) {
return null;
} else {
if (floatEquals(p12.y, p11.y)) {
y = p12.y;
x = (y - p21.y) / (p22.y - p21.y) * (p22.x - p21.x) + p21.x;
if (x >= Math.min(p11.x, p12.x) && x <= Math.max(p11.x, p12.x)
&& y >= Math.min(p21.y, p22.y) && y <= Math.max(p21.y, p22.y)) {
intersectionPoint = new Point((int) x, (int) y);
}
} else {
y = p21.y;
x = (y - p11.y) / (p12.y - p11.y) * (p12.x - p11.x) + p11.x;
if (x >= Math.min(p21.x, p22.x) && x <= Math.max(p21.x, p22.x)
&& y >= Math.min(p11.y, p12.y) && y <= Math.max(p11.y, p12.y)) {
intersectionPoint = new Point((int) x, (int) y);
}
}
}
}
}
return intersectionPoint;
}
private static Point getBoundsIntersection(Point tl, Point tr, Point br, Point bl, Point p1, Point p2) {
Point intersectionPoint = null;
if ((intersectionPoint = getBoundIntersection(tl, tr, p1, p2)) != null) {
return intersectionPoint;
} else if ((intersectionPoint = getBoundIntersection(tr, br, p1, p2)) != null) {
return intersectionPoint;
} else if ((intersectionPoint = getBoundIntersection(br, bl, p1, p2)) != null) {
return intersectionPoint;
} else if ((intersectionPoint = getBoundIntersection(bl, tl, p1, p2)) != null) {
return intersectionPoint;
}
return null;
}
}
And you can use it from MainActivity this way:
public class MainActivity extends AppCompatActivity {
private static final String MAP_VIEW_BUNDLE_KEY = "MapViewBundleKey";
static final LatLng KYIV = new LatLng(50.450311, 30.523730);
private EnhanchedMapView mMapView;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Bundle mapViewBundle = null;
if (savedInstanceState != null) {
mapViewBundle = savedInstanceState.getBundle(MAP_VIEW_BUNDLE_KEY);
}
mMapView = (EnhanchedMapView) findViewById(R.id.mapview);
mMapView.onCreate(mapViewBundle);
mMapView.getMapAsync(new OnMapReadyCallback() {
#Override
public void onMapReady(GoogleMap googleMap) {
mMapView.addMarker(new MarkerOptions().position(KYIV).title("Kyiv"));
}
});
}
#Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
Bundle mapViewBundle = outState.getBundle(MAP_VIEW_BUNDLE_KEY);
if (mapViewBundle == null) {
mapViewBundle = new Bundle();
outState.putBundle(MAP_VIEW_BUNDLE_KEY, mapViewBundle);
}
mMapView.onSaveInstanceState(mapViewBundle);
}
#Override
protected void onResume() {
super.onResume();
mMapView.onResume();
}
#Override
protected void onStart() {
super.onStart();
mMapView.onStart();
}
#Override
protected void onStop() {
super.onStop();
mMapView.onStop();
}
#Override
protected void onPause() {
mMapView.onPause();
super.onPause();
}
#Override
protected void onDestroy() {
mMapView.onDestroy();
super.onDestroy();
}
#Override
public void onLowMemory() {
super.onLowMemory();
mMapView.onLowMemory();
}
}
Where activity_main.xml can be like:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="{YOUR_PACKAGE_NAME}.MainActivity">
<{YOUR_PACKAGE_NAME}.EnhanchedMapView
android:id="#+id/mapview"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
/>
</RelativeLayout>
As a result you should get something like that:
So this bit of code seems to be the problem:
public void surfaceCreated(final SurfaceHolder sh) {
loop = new Loop();
loop.setMethod(new LoopMethod() {
public void callMethod() {
Canvas canvas = sh.lockCanvas();
synchronized(sh) {
draw(canvas);
}
sh.unlockCanvasAndPost(canvas);
}
});
loop.setRunning(true);
loop.start();
}
This works fine up until the user backs out of the application, or opens up the Recent Items tray. Upon which I get the following error:
java.lang.IllegalStateException: Surface has already been released.
which points to this line:
sh.unlockCanvasAndPost(canvas);
Why is this occurring and how do I fix it?
EDIT: ENTIRE PROBLEMATIC CLASS
public class SplashScreen extends SurfaceView implements SurfaceHolder.Callback {
private SplashActivity splashActivity;
private Loop loop;
//Loading gif info
private Movie loadingGif;
private long gifStart;
private int gifW, gifH;
private boolean gifDone;
public SplashScreen(Context context, SplashActivity splashActivity) {
super(context);
getHolder().addCallback(this);
this.splashActivity = splashActivity;
//Load gif in
InputStream gifStream = context.getResources().openRawResource(+ R.drawable.load);
loadingGif = Movie.decodeStream(gifStream);
gifW = loadingGif.width();
gifH = loadingGif.height();
gifDone = false;
}
private void beginLoading() {
}
public void surfaceCreated(final SurfaceHolder sh) {
loop = new Loop();
loop.setMethod(new LoopMethod() {
public void callMethod() {
Canvas canvas = sh.lockCanvas();
synchronized(sh) {
draw(canvas);
}
sh.unlockCanvasAndPost(canvas);
}
});
loop.setRunning(true);
loop.start();
}
public void draw(Canvas canvas) {
if(canvas != null) {
super.draw(canvas);
canvas.save();
canvas.drawColor(Color.rgb(69, 69, 69));
Paint p = new Paint();
p.setAntiAlias(true);
long now = android.os.SystemClock.uptimeMillis();
if(gifStart == 0)
gifStart = now;
if(loadingGif != null) {
int dur = loadingGif.duration();
if(!gifDone) {
int relTime = (int) ((now - gifStart) % dur);
loadingGif.setTime(relTime);
}
else
loadingGif.setTime(dur);
if(now - gifStart > dur && !gifDone) {
gifDone = true;
loadingGif.setTime(dur);
beginLoading();
}
float scaleX = getWidth()/gifW*1f;
float scaleY = getHeight()/gifH*1f;
float centerX = getWidth()/2/scaleX - gifW/2;
float centerY = getHeight()/2/scaleY - gifH/2;
canvas.scale(scaleX, scaleY);
loadingGif.draw(canvas, centerX, centerY, p);
canvas.restore();
p.setColor(Color.WHITE);
p.setTypeface(Typeface.create("Segoe UI", Typeface.BOLD));
p.setTextSize(UsefulMethods.spToPx(12));
String s = "Version " + BuildConfig.VERSION_NAME;
canvas.drawText(s, 10, getHeight() - 10, p);
}
}
else
System.out.println("not drawing :(");
}
public void surfaceDestroyed(SurfaceHolder sh) {
loop.setRunning(false);
}
public void surfaceChanged(SurfaceHolder sh, int format, int width, int height) {
System.out.println("call");
}
}
Check if visibility has changed, if it has pause/resume loop:
public void onVisibilityChanged(View view, int visibility) {
super.onVisibilityChanged(view, visibility);
if(loop != null)
if(visibility == VISIBLE)
if(!loop.isRunning())
loop.setRunning(true);
else;
else if(visibility == INVISIBLE)
if(loop.isRunning())
loop.setRunning(false);
}
In my case I covered sh.unlockCanvasAndPost with
if(sh.getSurface().isValid()){
sh.unlockCanvasAndPost(canvas);
}
If we will check isValid() source will see:
Returns true if this object holds a valid surface.
#return True if it holds a physical surface, so lockCanvas() will succeed.Otherwise returns false.
this is my code i m successfully zooming from center but i want to zoom in from the touch coordinates like instagram image zoom.I tried this code till now.Help me through it please.
ZoomImageActivity.java
public class ZoomImageActivity extends AppCompatActivity {
ImageView ivBG;
private ZoomImageHelper imageZoomHelper;
private View zoomableView = null;
View.OnTouchListener zoomTouchListener = new View.OnTouchListener() {
#Override
public boolean onTouch(View view, MotionEvent ev) {
switch (ev.getAction() & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_MOVE:
if (ev.getPointerCount() == 2 && zoomableView == null)
zoomableView = view;
break;
}
return false;
}
};
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_zoom_image);
ivBG = (ImageView) findViewById(R.id.ivBG);
ivBG.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
}
});
ivBG.setOnTouchListener(zoomTouchListener);
imageZoomHelper = new ZoomImageHelper(this, tvParam);
imageZoomHelper.addOnZoomListener(new ZoomImageHelper.OnZoomListener() {
#Override
public void onImageZoomStarted(View view) {
}
#Override
public void onImageZoomEnded(View view) {
zoomableView = null;
}
});
}
#Override
public boolean dispatchTouchEvent(MotionEvent ev) {
return imageZoomHelper.onDispatchTouchEvent(ev, zoomableView) || super.dispatchTouchEvent(ev);
}}
ZoomImageHelper.java
public class ZoomImageHelper {
private View zoomableView = null;
private ViewGroup parentOfZoomableView;
private ViewGroup.LayoutParams zoomableViewLP;
private FrameLayout.LayoutParams zoomableViewFrameLP;
private Dialog dialog;
private View placeholderView;
private int viewIndex;
private View darkView;
private double originalDistance;
private int[] twoPointCenter;
private int[] originalXY;
private WeakReference<Activity> activityWeakReference;
private boolean isAnimatingDismiss = false;
private List<OnZoomListener> zoomListeners = new ArrayList<>();
public ZoomImageHelper(Activity activity) {
this.activityWeakReference = new WeakReference<>(activity);
}
public boolean onDispatchTouchEvent(MotionEvent ev, View view) {
Activity activity;
if ((activity = activityWeakReference.get()) == null)
return false;
if (ev.getPointerCount() == 2) {
if (zoomableView == null) {
if (view != null) {
zoomableView = view;
// get view's original location relative to the window
originalXY = new int[2];
view.getLocationInWindow(originalXY);
// this FrameLayout will be the zoomableView's temporary parent
FrameLayout frameLayout = new FrameLayout(view.getContext());
// this view is to gradually darken the backdrop as user zooms
darkView = new View(view.getContext());
darkView.setBackgroundColor(Color.BLACK);
darkView.setAlpha(0f);
// adding darkening backdrop to the frameLayout
frameLayout.addView(darkView, new FrameLayout.LayoutParams(
FrameLayout.LayoutParams.MATCH_PARENT,
FrameLayout.LayoutParams.MATCH_PARENT));
// the Dialog that will hold the FrameLayout
dialog = new Dialog(activity,
android.R.style.Theme_Translucent_NoTitleBar_Fullscreen);
dialog.addContentView(frameLayout,
new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT));
dialog.show();
// get the parent of the zoomable view and get it's index and layout param
parentOfZoomableView = (ViewGroup) zoomableView.getParent();
viewIndex = parentOfZoomableView.indexOfChild(zoomableView);
this.zoomableViewLP = zoomableView.getLayoutParams();
// this is the new layout param for the zoomableView
zoomableViewFrameLP = new FrameLayout.LayoutParams(
view.getWidth(), view.getHeight());
zoomableViewFrameLP.leftMargin = originalXY[0];
zoomableViewFrameLP.topMargin = originalXY[1];
// this view will hold the zoomableView's position temporarily
placeholderView = new View(activity);
// setting placeholderView's background to zoomableView's drawingCache
// this avoids flickering when adding/removing views
zoomableView.setDrawingCacheEnabled(true);
BitmapDrawable placeholderDrawable = new BitmapDrawable(
activity.getResources(),
Bitmap.createBitmap(zoomableView.getDrawingCache()));
if (Build.VERSION.SDK_INT >= 16) {
placeholderView.setBackground(placeholderDrawable);
} else {
placeholderView.setBackgroundDrawable(placeholderDrawable);
}
// placeholderView takes the place of zoomableView temporarily
parentOfZoomableView.addView(placeholderView, zoomableViewLP);
// zoomableView has to be removed from parent view before being added to it's
// new parent
parentOfZoomableView.removeView(zoomableView);
frameLayout.addView(zoomableView, zoomableViewFrameLP);
// using a post to remove placeholder's drawing cache
zoomableView.post(new Runnable() {
#Override
public void run() {
if (dialog != null) {
if (Build.VERSION.SDK_INT >= 16) {
placeholderView.setBackground(null);
} else {
placeholderView.setBackgroundDrawable(null);
}
zoomableView.setDrawingCacheEnabled(false);
}
}
});
// Pointer variables to store the original touch positions
MotionEvent.PointerCoords pointerCoords1 = new MotionEvent.PointerCoords();
ev.getPointerCoords(0, pointerCoords1);
MotionEvent.PointerCoords pointerCoords2 = new MotionEvent.PointerCoords();
ev.getPointerCoords(1, pointerCoords2);
// storing distance between the two positions to be compared later on for
// zooming
originalDistance = (int) getDistance(pointerCoords1.x, pointerCoords2.x,
pointerCoords1.y, pointerCoords2.y);
// storing center point of the two pointers to move the view according to the
// touch position
twoPointCenter = new int[]{
(int) ((pointerCoords2.x + pointerCoords1.x) / 2),
(int) ((pointerCoords2.y + pointerCoords1.y) / 2)
};
sendZoomEventToListeners(zoomableView, true);
return true;
}
} else {
MotionEvent.PointerCoords pointerCoords1 = new MotionEvent.PointerCoords();
ev.getPointerCoords(0, pointerCoords1);
MotionEvent.PointerCoords pointerCoords2 = new MotionEvent.PointerCoords();
ev.getPointerCoords(1, pointerCoords2);
int[] newCenter = new int[]{
(int) ((pointerCoords2.x + pointerCoords1.x) / 2),
(int) ((pointerCoords2.y + pointerCoords1.y) / 2)
};
int currentDistance = (int) getDistance(pointerCoords1.x, pointerCoords2.x,
pointerCoords1.y, pointerCoords2.y);
double pctIncrease = (currentDistance - originalDistance) / originalDistance;
zoomableView.setScaleX((float) (1 + pctIncrease));
zoomableView.setScaleY((float) (1 + pctIncrease));
updateZoomableViewMargins(newCenter[0] - twoPointCenter[0] + originalXY[0],
newCenter[1] - twoPointCenter[1] + originalXY[1]);
darkView.setAlpha((float) (pctIncrease / 8));
return true;
}
} else {
if (zoomableView != null && !isAnimatingDismiss) {
isAnimatingDismiss = true;
ValueAnimator valueAnimator = ValueAnimator.ofFloat(0f, 1f);
valueAnimator.setDuration(activity.getResources()
.getInteger(android.R.integer.config_shortAnimTime));
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
float scaleYStart = zoomableView.getScaleY();
float scaleXStart = zoomableView.getScaleX();
int leftMarginStart = zoomableViewFrameLP.leftMargin;
int topMarginStart = zoomableViewFrameLP.topMargin;
float alphaStart = darkView.getAlpha();
float scaleYEnd = 1f;
float scaleXEnd = 1f;
int leftMarginEnd = originalXY[0];
int topMarginEnd = originalXY[1];
float alphaEnd = 0f;
#Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
float animatedFraction = valueAnimator.getAnimatedFraction();
if (animatedFraction < 1) {
zoomableView.setScaleX(((scaleXEnd - scaleXStart) * animatedFraction) +
scaleXStart);
zoomableView.setScaleY(((scaleYEnd - scaleYStart) * animatedFraction) +
scaleYStart);
updateZoomableViewMargins(
((leftMarginEnd - leftMarginStart) * animatedFraction) +
leftMarginStart,
((topMarginEnd - topMarginStart) * animatedFraction) +
topMarginStart);
darkView.setAlpha(((alphaEnd - alphaStart) * animatedFraction) +
alphaStart);
} else {
dismissDialogAndViews();
}
}
});
valueAnimator.start();
return true;
}
}
return false;
}
void updateZoomableViewMargins(float left, float top) {
if (zoomableView != null && zoomableViewFrameLP != null) {
zoomableViewFrameLP.leftMargin = (int) left;
zoomableViewFrameLP.topMargin = (int) top;
zoomableView.setLayoutParams(zoomableViewFrameLP);
}
}
/**
* Dismiss dialog and set views to null for garbage collection
*/
private void dismissDialogAndViews() {
sendZoomEventToListeners(zoomableView, false);
if (zoomableView != null) {
zoomableView.setVisibility(View.VISIBLE);
zoomableView.setDrawingCacheEnabled(true);
BitmapDrawable placeholderDrawable = new BitmapDrawable(
zoomableView.getResources(),
Bitmap.createBitmap(zoomableView.getDrawingCache()));
if (Build.VERSION.SDK_INT >= 16) {
placeholderView.setBackground(placeholderDrawable);
} else {
placeholderView.setBackgroundDrawable(placeholderDrawable);
}
ViewGroup parent = (ViewGroup) zoomableView.getParent();
parent.removeView(zoomableView);
this.parentOfZoomableView.addView(zoomableView, viewIndex, zoomableViewLP);
this.parentOfZoomableView.removeView(placeholderView);
zoomableView.setDrawingCacheEnabled(false);
zoomableView.post(new Runnable() {
#Override
public void run() {
dismissDialog();
}
});
zoomableView = null;
} else {
dismissDialog();
}
isAnimatingDismiss = false;
}
public void addOnZoomListener(OnZoomListener onZoomListener) {
zoomListeners.add(onZoomListener);
}
public void removeOnZoomListener(OnZoomListener onZoomListener) {
zoomListeners.remove(onZoomListener);
}
private void sendZoomEventToListeners(View zoomableView, boolean zoom) {
for (OnZoomListener onZoomListener : zoomListeners) {
if (zoom)
onZoomListener.onImageZoomStarted(zoomableView);
else
onZoomListener.onImageZoomEnded(zoomableView);
}
}
private void dismissDialog() {
if (dialog != null) {
dialog.dismiss();
dialog = null;
}
darkView = null;
resetOriginalViewAfterZoom();
}
private void resetOriginalViewAfterZoom() {
zoomableView.invalidate();
zoomableView = null;
}
/**
* Get distance between two points
*
* #param x1
* #param x2
* #param y1
* #param y2
* #return distance
*/
private double getDistance(double x1, double x2, double y1, double y2) {
return Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2));
}
public interface OnZoomListener {
void onImageZoomStarted(View view);
void onImageZoomEnded(View view);
}}
I need the image to zoom and pan from its touch coordinates not from center.. Thanks in advance.
I'm new to the game development in Android. This is what I'm trying to do
Render a background (green field) with items (items are behind the background but I've made them appear at the top for now)
Drawing an additional image (dig hole) on touch
Doing a collision test to see if user has found the item
I'm stuck with the second step as I'm not able to get it working at all. Blank screen appears as soon as touch event method invoked. Only a dig hole image is drawn on the blank screen. I've read thru the documents but couldn't figure out what's going wrong here.
Here is my code
class DrawSurface extends android.view.SurfaceView implements SurfaceHolder.Callback, View.OnTouchListener {
Bitmap mBMPField;
Bitmap mBMPHole;
SurfaceHolder surfaceHolder;
float x = 0;
float y = 0;
boolean touched = false;
public DrawSurface(Context context) {
super(context);
getHolder().addCallback(this);
setOnTouchListener(this);
}
public DrawSurface(Context context, AttributeSet attrSet) {
super(context, attrSet);
getHolder().addCallback(this);
setOnTouchListener(this);
}
public DrawSurface(Context context, AttributeSet attrSet, int styleAttr) {
super(context, attrSet, styleAttr);
getHolder().addCallback(this);
setOnTouchListener(this);
}
#Override
public void surfaceCreated(SurfaceHolder holder) {
Log.i("a", "Inside surfaceCreated");
setWillNotDraw(false);
mBMPField = BitmapFactory.decodeResource(getResources(),R.drawable.field);
mBMPHole = BitmapFactory.decodeResource(getResources(),R.drawable.hole);
surfaceHolder = holder;
Canvas canvas = null;
try {
canvas = surfaceHolder.lockCanvas();
Log.d(this.getClass().getName(), String.valueOf(canvas.hashCode()));
canvas.drawBitmap(mBMPField, 0, 0, null);
Paint paintText = new Paint();
paintText.setColor(Color.WHITE);
paintText.setStyle(Paint.Style.FILL);
paintText.setAntiAlias(true);
paintText.setTextSize(20);
ArrayList<Item> mItems = loadItems();
for (Item item : mItems) {
item.setX((int) (Math.random() * this.getWidth()));
item.setY((int) (Math.random() * this.getHeight()));
canvas.drawText(item.getText(), item.getX(), item.getY(), paintText);
}
}
finally
{
if(canvas!=null) {
surfaceHolder.unlockCanvasAndPost(canvas);
}
}
}
#Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
}
#Override
public void surfaceDestroyed(SurfaceHolder holder) {
}
#Override
public boolean onTouch(View v, MotionEvent event) {
Log.i("a", "Inside onTouch");
if(event.getAction() == MotionEvent.ACTION_DOWN) {
if (surfaceHolder.getSurface().isValid()) {
touched = true;
x = event.getX();
y = event.getY();
Canvas canvas = null;
try {
canvas = surfaceHolder.lockCanvas();
Log.d(this.getClass().getName(), String.valueOf(canvas.hashCode()));
canvas.drawBitmap(mBMPHole, x - mBMPHole.getWidth() / 2, y - mBMPHole.getHeight() / 2, null);
}
finally {
if(canvas!=null) {
surfaceHolder.unlockCanvasAndPost(canvas);
}
}
}
}
return false;
}
private class Item {
private int x;
private int y;
private String text;
private boolean found;
public boolean isFound() {
return found;
}
public void setFound(boolean found) {
this.found = found;
}
public String getText() {
return text;
}
public void setText(String text) {
this.text = text;
}
public int getX() {
return x;
}
public void setX(int x) {
this.x = x;
}
public int getY() {
return y;
}
public void setY(int y) {
this.y = y;
}
}
private ArrayList<Item> loadItems()
{
InputStream input = getResources().openRawResource(R.raw.items);
BufferedReader reader = null;
ArrayList<Item> items = new ArrayList<Item>();
String line;
try
{
reader = new BufferedReader(new InputStreamReader(input));
while((line = reader.readLine()) != null)
{
Item item = new Item();
item.setText(line);
items.add(item);
}
}
catch (Exception e)
{
Log.e("MainActivity", "Reading list of Items failed!", e);
}
finally {
try
{
if (reader != null) reader.close();
}
catch (Exception e)
{
Log.e("MainActivity", "Error closing file reader.", e);
}
}
return items;
}
}
Any advice would greatly helpful here
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
}
}
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.