Created
June 8, 2012 11:03
-
-
Save eliantor/2894999 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class ImageMapView extends ImageView{ | |
private final static int INVALID_POINTER_ID = -1; | |
private final ArrayList<Area> mAreaList = new ArrayList<ImageMapView.Area>(); | |
private final SparseArray<Area> mIdToArea = new SparseArray<ImageMapView.Area>(); | |
private OnClickAreaListener mListener; | |
private int mTouchId = INVALID_POINTER_ID; | |
private float mTouchX; | |
private float mTouchY; | |
private final float mSquaredTouchSlop; | |
private Area mTouchedArea; | |
private float mOriginalWidth; | |
private float mOriginalHeight; | |
private float mScaledWidth; | |
private float mScaledHeight; | |
private float mDipScaling; | |
/// Drawing | |
private final static boolean DEBUG=false; | |
private final Paint protoPaint; | |
private final static int[] CANDYCANE={ 0x55FF0000,0x5500FF00,0x550000FF}; | |
public ImageMapView(Context context) { | |
this(context,null,0); | |
} | |
public ImageMapView(Context context, AttributeSet attrs) { | |
this(context, attrs,0); | |
} | |
public ImageMapView(Context context, AttributeSet attrs, int defStyle) { | |
super(context, attrs, defStyle); | |
ViewConfiguration config = ViewConfiguration.get(context); | |
float slop=config.getScaledTouchSlop(); | |
mSquaredTouchSlop=slop*slop; | |
mTouchedArea=null; | |
protoPaint = new Paint(Paint.ANTI_ALIAS_FLAG); | |
protoPaint.setStyle(Style.FILL); | |
mDipScaling = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 1.0f, getResources().getDisplayMetrics()); | |
TypedArray a = context.obtainStyledAttributes(attrs,R.styleable.ImageMapView); | |
String mapName=a.getString(R.styleable.ImageMapView_map); | |
if(mapName!=null){ | |
loadMap(mapName); | |
} | |
} | |
private XmlResourceParser openParser(){ | |
Resources res = getContext().getResources(); | |
return res.getXml(R.xml.maps); | |
} | |
private void loadMap(String mapName){ | |
XmlResourceParser xpp = null; | |
boolean loading = false; | |
try{ | |
xpp=openParser(); | |
int event=xpp.getEventType(); | |
while(event!=XmlPullParser.END_DOCUMENT){ | |
if(event == XmlPullParser.START_TAG){ | |
String tag = xpp.getName(); | |
if(tag.equals("map")){ | |
loading = mapName.equals(xpp.getAttributeValue(null,"id")); | |
} | |
if(loading){ | |
if(tag.equals("area")){ | |
String shape=xpp.getAttributeValue(null,"shape"); | |
String coords=xpp.getAttributeValue(null,"coords"); | |
String id=xpp.getAttributeValue(null,"id"); | |
if((shape!=null)&&(coords!=null)&&(id!=null)){ | |
addShape(shape,coords,id); | |
} | |
} | |
} | |
}else if(event == XmlPullParser.END_TAG){ | |
String tag = xpp.getName(); | |
if(tag.equals("map")) loading = false; | |
} | |
event=xpp.next(); | |
} | |
}catch(XmlPullParserException ex){ | |
}catch(IOException ex){ | |
}finally{ | |
if(xpp!=null){ | |
xpp.close(); | |
} | |
} | |
} | |
private void addShape(String shape,String coords,String id){ | |
Area a = null; | |
String rid=id.replace("@+id/", ""); | |
int cid=0; | |
try{ | |
Class<R.id> res = R.id.class; | |
Field field = res.getField(rid); | |
cid = field.getInt(null); | |
}catch(Exception e){ | |
cid=0; | |
} | |
if(cid!=0){ | |
if(shape.equals("rect")){ | |
String[] v = coords.split(","); | |
if(v.length==4){ | |
a = new RectArea(cid, Float.parseFloat(v[0]), | |
Float.parseFloat(v[1]), | |
Float.parseFloat(v[2]), | |
Float.parseFloat(v[3])); | |
}else{ | |
throw new RuntimeException("Wrong number of coordinates for rectangular area"); | |
} | |
}else if(shape.equals("circle")){ | |
String[] v = coords.split(","); | |
if(v.length == 3){ | |
a = new CircleArea(cid, Float.parseFloat(v[0]), | |
Float.parseFloat(v[1]), | |
Float.parseFloat(v[2])); | |
}else{ | |
throw new RuntimeException("Wrong number of coordinates for circular area"); | |
} | |
}else if(shape.equals("poly")){ | |
a = new PolyArea(cid, coords); | |
}else{ | |
throw new RuntimeException("Unsupported imagemap shape type"); | |
} | |
if(a!=null){ | |
mAreaList.add(a); | |
mIdToArea.put(a.id, a); | |
} | |
} | |
} | |
public void setOnClickAreaListener(OnClickAreaListener listener){ | |
this.mListener=listener; | |
} | |
@Override | |
protected void onSizeChanged(int w, int h, int oldw, int oldh) { | |
super.onSizeChanged(w, h, oldw, oldh); | |
Drawable dr=getDrawable(); | |
Rect bounds = dr.getBounds(); | |
mScaledWidth = w; | |
mScaledHeight = h; | |
mOriginalWidth= dr.getIntrinsicWidth(); | |
mOriginalHeight= dr.getIntrinsicHeight(); | |
Log.d("IMGMAP", "w: "+mOriginalWidth+", scaled:"+mScaledWidth+" h:"+mOriginalHeight+", scaled: "+mScaledHeight); | |
Log.d("IMGMAP", "x: "+bounds.left+ " y:"+bounds.top); | |
} | |
@Override | |
protected void onDraw(Canvas canvas) { | |
super.onDraw(canvas); | |
if(DEBUG){ | |
canvas.save(); | |
float[] values=new float[9]; | |
getImageMatrix().getValues(values); | |
float xScale = values[Matrix.MSCALE_X]; | |
float yScale = values[Matrix.MSCALE_Y]; | |
canvas.scale(xScale*mDipScaling, yScale*mDipScaling, 0, 0); | |
int i=0; | |
for(Area a:mAreaList){ | |
protoPaint.setColor(CANDYCANE[(i++)%CANDYCANE.length]); | |
a.draw(canvas, protoPaint); | |
} | |
canvas.restore(); | |
} | |
} | |
@Override | |
public boolean onTouchEvent(MotionEvent event) { | |
final int action = event.getAction(); | |
boolean handled= false; | |
switch(action & MotionEvent.ACTION_MASK){ | |
case MotionEvent.ACTION_DOWN: | |
handled= startHandling(event); | |
break; | |
case MotionEvent.ACTION_POINTER_DOWN: | |
case MotionEvent.ACTION_CANCEL: | |
mTouchId=INVALID_POINTER_ID; | |
mTouchedArea=null; | |
handled=false; | |
break; | |
case MotionEvent.ACTION_MOVE: | |
if(mTouchId!=INVALID_POINTER_ID){ | |
final int index = event.findPointerIndex(mTouchId); | |
final int count = event.getPointerCount(); | |
if(index>=0&& index<count){ | |
final float xPos=event.getX(index); | |
final float yPos=event.getY(index); | |
final float dx=xPos-mTouchX; | |
final float dy=yPos-mTouchY; | |
handled= (dx*dx+dy*dy)<mSquaredTouchSlop; | |
if(!handled){ | |
mTouchId=INVALID_POINTER_ID; | |
mTouchedArea=null; | |
} | |
}else{ | |
mTouchId=INVALID_POINTER_ID; | |
mTouchedArea=null; | |
handled=false; | |
} | |
}else{ | |
handled=false; | |
} | |
break; | |
case MotionEvent.ACTION_UP: | |
handled= maybeClick(event); | |
break; | |
} | |
return handled||super.onTouchEvent(event); | |
} | |
private boolean maybeClick(MotionEvent event) { | |
boolean handled=false; | |
if(mTouchId != INVALID_POINTER_ID){ | |
performClickArea(this.mTouchedArea.id); | |
handled=true; | |
} | |
mTouchId=INVALID_POINTER_ID; | |
mTouchedArea=null; | |
return handled; | |
} | |
public void performClickArea(int id){ | |
if(mListener!=null){ | |
playSoundEffect(SoundEffectConstants.CLICK); | |
mListener.onClickArea(id); | |
} | |
} | |
private boolean startHandling(MotionEvent event) { | |
final float xPos=event.getX(); | |
final float yPos=event.getY(); | |
float[] values=new float[9]; | |
getImageMatrix().getValues(values); | |
float xScale = values[Matrix.MSCALE_X]*mDipScaling; //TODO Why this factor? | |
float yScale = values[Matrix.MSCALE_Y]*mDipScaling; //TODO May depend on dp configuration | |
for(Area a:mAreaList){ | |
if(a.isInside(xPos/xScale, yPos/yScale)){ | |
mTouchedArea=a; | |
break; | |
} | |
} | |
if(mTouchedArea!=null){ | |
mTouchX=xPos; | |
mTouchY=yPos; | |
mTouchId=event.getPointerId(0); | |
return true; | |
} | |
return false; | |
} | |
public static interface OnClickAreaListener{ | |
public void onClickArea(int id); | |
} | |
private abstract static class Area{ | |
final int id; | |
final Path areaPath; | |
Area(int id){ | |
this.id=id; | |
areaPath=new Path(); | |
} | |
abstract boolean isInside(final float x,final float y); | |
void draw(Canvas c,Paint p){ | |
c.drawPath(areaPath, p); | |
} | |
} | |
private static class RectArea extends Area{ | |
private final RectF mBounding=new RectF(); | |
RectArea(int id,final float l,final float t,final float r,final float b){ | |
super(id); | |
mBounding.set(l, t, r, b); | |
} | |
@Override | |
boolean isInside(float x, float y) { | |
return mBounding.contains(x, y); | |
} | |
} | |
private static class CircleArea extends Area{ | |
private final float centerX; | |
private final float centerY; | |
private final float radius; | |
CircleArea(int id,float cx,float cy,float r){ | |
super(id); | |
this.centerX=cx; | |
this.centerY=cy; | |
this.radius=r; | |
} | |
@Override | |
boolean isInside(float x, float y) { | |
final float dx=x-centerX; | |
final float dy=y-centerY; | |
return FloatMath.sqrt(dx*dx+dy*dy)<radius; | |
} | |
} | |
private static class PolyArea extends Area{ | |
private final Region mRegion; | |
public PolyArea(int id,String coordString) { | |
super(id); | |
String[] v = coordString.split(","); | |
if(v.length%2 != 0)throw new RuntimeException("Error parsing poly area"); | |
int sides=v.length/2; | |
if(sides>0){ | |
areaPath.moveTo(Float.parseFloat(v[0]), Float.parseFloat(v[1])); | |
int j; | |
for(int i=1;i<sides;i++){ | |
j=i*2; | |
float xC = Float.parseFloat(v[j]); | |
float yC = Float.parseFloat(v[j+1]); | |
areaPath.lineTo(xC, yC); | |
} | |
areaPath.close(); | |
} | |
mRegion=new Region(); | |
RectF bounds = new RectF(); | |
areaPath.computeBounds(bounds, true); | |
mRegion.setPath(areaPath, new Region((int)bounds.left, (int)bounds.top, (int)bounds.right, (int)bounds.bottom)); | |
} | |
@Override | |
boolean isInside(float x, float y) { | |
return mRegion.contains((int)x, (int)y); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment