Is there a way to getPixel(x,y) without a bitmap? [duplicate] - android

I am trying to make each slice of the pie a button. The pie is a bunch of vector drawables in an image view. I don't necessarily need the actual pie slices to be clicked. I was thinking of using Path to draw a transparent shape and place it on top and make that the button, but from what I understand, drawables aren't clickable.
I read one blog post that apparently used paths to make a custom shaped image view, and I know image views are clickable, but it seems like with the implementation in the blog post the image views are still rectangular, but the bitmaps the blogger was using in the example were just trimmed to a custom shape inside the image view. This is the post I'm referring to: http://www.androidhub4you.com/2014/10/android-custom-shape-imageview-rounded.html
Please explain this to me like a five year old. I'm relatively new to programming. Were it not for Android Studio's automatic everything, I would not be here.
Thank you.

You can just using drawArc and drawCircle to draw a radial menu, and using distance between touch point and center point and angle to detect which slice is currently being click. I wrote a Sample for you:
public class RadioButtons extends View {
//the number of slice
private int mSlices = 6;
//the angle of each slice
private int degreeStep = 360 / mSlices;
private int quarterDegreeMinus = -90;
private float mOuterRadius;
private float mInnerRadius;
//using radius square to prevent square root calculation
private float outerRadiusSquare;
private float innerRadiusSquare;
private Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
private RectF mSliceOval = new RectF();
private static final double quarterCircle = Math.PI / 2;
private float innerRadiusRatio = 0.3F;
//color for your slice
private int[] colors = new int[]{Color.GREEN, Color.GRAY, Color.BLUE, Color.CYAN, Color.DKGRAY, Color.RED};
private int mCenterX;
private int mCenterY;
private OnSliceClickListener mOnSliceClickListener;
private int mTouchSlop;
private boolean mPressed;
private float mLatestDownX;
private float mLatestDownY;
public interface OnSliceClickListener{
void onSlickClick(int slicePosition);
}
public RadioButtons(Context context){
this(context, null);
}
public RadioButtons(Context context, AttributeSet attrs){
this(context, attrs, 0);
}
public RadioButtons(Context context, AttributeSet attrs, int defStyle){
super(context, attrs, defStyle);
ViewConfiguration viewConfiguration = ViewConfiguration.get(context);
mTouchSlop = viewConfiguration.getScaledTouchSlop();
mPaint.setStrokeWidth(10);
}
public void setOnSliceClickListener(OnSliceClickListener onSliceClickListener){
mOnSliceClickListener = onSliceClickListener;
}
#Override
public void onSizeChanged(int w, int h, int oldw, int oldh){
mCenterX = w / 2;
mCenterY = h / 2;
mOuterRadius = mCenterX > mCenterY ? mCenterY : mCenterX;
mInnerRadius = mOuterRadius * innerRadiusRatio;
outerRadiusSquare = mOuterRadius * mOuterRadius;
innerRadiusSquare = mInnerRadius * mInnerRadius;
mSliceOval.left = mCenterX - mOuterRadius;
mSliceOval.right = mCenterX + mOuterRadius;
mSliceOval.top = mCenterY - mOuterRadius;
mSliceOval.bottom = mCenterY + mOuterRadius;
}
#Override
public boolean onTouchEvent(MotionEvent event){
float currX = event.getX();
float currY = event.getY();
switch(event.getActionMasked()){
case MotionEvent.ACTION_DOWN:
mLatestDownX = currX;
mLatestDownY = currY;
mPressed = true;
break;
case MotionEvent.ACTION_MOVE:
if(Math.abs(currX - mLatestDownX) > mTouchSlop || Math.abs(currY - mLatestDownY) > mTouchSlop) mPressed = false;
break;
case MotionEvent.ACTION_UP:
if(mPressed){
int dx = (int) currX - mCenterX;
int dy = (int) currY - mCenterY;
int distanceSquare = dx * dx + dy * dy;
//if the distance between touchpoint and centerpoint is smaller than outerRadius and longer than innerRadius, then we're in the clickable area
if(distanceSquare > innerRadiusSquare && distanceSquare < outerRadiusSquare){
//get the angle to detect which slice is currently being click
double angle = Math.atan2(dy, dx);
if(angle >= -quarterCircle && angle < 0){
angle += quarterCircle;
}else if(angle >= -Math.PI && angle < -quarterCircle){
angle += Math.PI + Math.PI + quarterCircle;
}else if(angle >= 0 && angle < Math.PI){
angle += quarterCircle;
}
double rawSliceIndex = angle / (Math.PI * 2) * mSlices;
if(mOnSliceClickListener != null){
mOnSliceClickListener.onSlickClick((int) rawSliceIndex);
}
}
}
break;
}
return true;
}
#Override
public void onDraw(Canvas canvas){
int startAngle = quarterDegreeMinus;
//draw slice
for(int i = 0; i < mSlices; i++){
mPaint.setStyle(Paint.Style.FILL);
mPaint.setColor(colors[i % colors.length]);
canvas.drawArc(mSliceOval, startAngle, degreeStep, true, mPaint);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setColor(Color.WHITE);
canvas.drawArc(mSliceOval, startAngle, degreeStep, true, mPaint);
startAngle += degreeStep;
}
//draw center circle
mPaint.setStyle(Paint.Style.FILL);
mPaint.setColor(Color.BLACK);
canvas.drawCircle(mCenterX, mCenterY, mInnerRadius, mPaint);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setColor(Color.WHITE);
canvas.drawCircle(mCenterX, mCenterY, mInnerRadius, mPaint);
}
}

Related

Android: How to draw circle within two point?

this is my current code
Path path_eclipse = new Path();
float radius = (float) (Math.sqrt(Math.pow(r.stopX - r.startX, 2.0f) + Math.pow(r.stopY - r.startY, 2.0f)) / 2.0f);
path_eclipse.addCircle(r.startX, r.startY, radius, Path.Direction.CCW);
canvas.drawPath(path_eclipse, paint);
with this code I am getting as output:
But I want to draw circle like this:
UPDATED Source code: This source code worked in my case
[SOLVED]
[OnDraw]
#Override
protected void onDraw(Canvas canvas) {
Path path_eclipse = new Path();
float centerX = (r.startX + r.stopX) /2;
float centerY = (r.startY + r.stopY) /2;
float radius = (float)Math.sqrt((r.stopX - r.startX)*(r.stopX - r.startX)+(r.stopY - r.startY)*(r.stopY - r.startY));
path_eclipse.addCircle(centerX, centerY, radius/2, Path.Direction.CCW);
canvas.drawPath(path_eclipse,paint);
}
[OnTouchEvent]
#Override
public boolean onTouchEvent(MotionEvent event) {
float eventX = event.getX();
float eventY = event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
startX = eventX;
startY = eventY;
return true;
case MotionEvent.ACTION_MOVE:
stopX = eventX;
stopY = eventY;
break;
case MotionEvent.ACTION_UP:
stopX = eventX;
stopY = eventY;
break;
default:
return false;
}
invalidate();
return true;
}
The first two paramateres of addCircle are the x and y coordinates of the center. Assuming A and B are the furthest distance from each other on the circle you want, then the center should a point equidistant to both, hence:
float centerX = (pointA.x + pointB.x) /2
float centerY = (pointA.y + pointB.y) /2
And your radius should be, the distance between A and B, thus:
float radius = (Math.sqrt(Math.pow(x2−x1, 2) + Math.pow(y2−y1, 2))) / 2
Mid points
int mx=(r.stopX + r.startX)/2;
int my= (r.stopy+r.startY)/2;
Radius
float radius = Math.sqrt(Math.pow(r.stopX - my, 2)
+ Math.pow(r.stopY - my, 2));
Edited
I have used code below
public class CustomView extends View {
private Paint paint;
private Circle circle;
private List<Point> points;
public final int CIRCLE_BETWEEN_TWO_POINTS = 1;
private int viewType;
{
paint = new Paint();
points = new ArrayList<>();
}
public CustomView(Context context) {
super(context);
}
public CustomView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public CustomView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
#Override
protected void onDraw(Canvas canvas) {
switch (viewType) {
case CIRCLE_BETWEEN_TWO_POINTS:
drawView(canvas);
break;
}
}
private void drawView(Canvas canvas){
for(Point point:points){
drawCircle(canvas,new Circle(point.x,point.y,10),false);
}
drawCircle(canvas,circle,true);
}
private void drawCircle(Canvas canvas,Circle circle, boolean isStroke){
paint.reset();
paint.setAntiAlias(true);
if(isStroke){
paint.setStrokeWidth(5);
paint.setColor(Color.BLACK);
paint.setStyle(Paint.Style.STROKE);
}else {
paint.setColor(Color.RED);
paint.setStyle(Paint.Style.FILL);
}
canvas.drawCircle(circle.getX(), circle.getY(), circle.getRadius(), paint);
}
public void drawCircleBetweenTwoPoints(int x1, int y1, int x2, int y2) {
viewType = CIRCLE_BETWEEN_TWO_POINTS;
points.clear();
points.add(new Point(x1, y1));
points.add(new Point(x2, y2));
int mx = (x1 + x2) / 2;
int my = (y1 + y2) / 2;
double radius = Math.sqrt(Math.pow(x1 - mx, 2)
+ Math.pow(y1 - my, 2));
circle=new Circle(mx,my,(int)radius);
invalidate();
}
}
And called method as
customView.drawCircleBetweenTwoPoints(500,200,100,600);
and its working for me
Click to see output
If you want to draw circle like this, you must know 3 points, then calculate center with radius. Because two points cannot uniquely determine a circle. You can find only whole line of possible centers.
On the other hand, if we consider that two points are exactly opposite. Then you should calculate center by midpoint formula:
M = [(x1 + x2 / 2), (y1 + y2 / 2)]
Next, you can do the same thing as at first case. First, calculate radius and then draw circle.
Assuming, that A and B are exactly opposite to each other, the line segment defined by them will pass through the centre of the circle (C), and the latter will cut AB in half.
Thus:
You calculate then distance of the two points, and divide by two. So you'll have the radius.
The centre of the circle will be exactly halfway between the two: x=(x1+x2)/2 and y=(y1+y2)/2.
If they happen to be just random points, you can use one of these techniques:
Link
Link
In this case however, your problem is slightly tricky, as two points and a radius will not determine one circle unambiguously: the problem will have two solutions.

How to bring canvas on top of other views

I am using magnifier view for my application which is a library I got from here :
https://github.com/nomanr/android-image-magnifier
I have modified this class to extend FrameLayout (It was ImageView before) to work on my FrameLayout.
It's working well except the painted canvas view stays back of all the views which are added in that custom view.
How to bring that canvas on front of those added views?
Custom view class that i am using :
public class ImageMagnifier extends FrameLayout {
private PointF zoomPos;
private boolean zooming = false;
private Matrix matrix;
private Paint paint;
private Bitmap bitmap;
private BitmapShader shader;
private int sizeOfMagnifier = 300;
int cheight, cwidth;
Context con;
C c = C.getInstance();
public ImageMagnifier(Context context) {
super(context);
init();
con=context;
}
public ImageMagnifier(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
con=context;
}
public ImageMagnifier(Context context, AttributeSet attrs) {
super(context, attrs);
init();
con=context;
}
private void init() {
zoomPos = new PointF(0, 0);
matrix = new Matrix();
paint = new Paint();
cwidth = c.Width * 109 / 1280;
cheight = cwidth * 134 / 109;
}
public void otherTouch(int x, int y) {
if (x > c.getwidth1(28) && x < c.getwidth1(921) && y > c.getheight1(135) && y < c.getheight1(560)) {
zoomPos.x = x - 10;
zoomPos.y = y - 75;
zooming = true;
this.invalidate();
} else {
RemoveMagnifire();
}
}
public void RemoveMagnifire() {
zooming = false;
this.invalidate();
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (!zooming) {
buildDrawingCache();
} else {
bitmap = getDrawingCache();
shader = new BitmapShader(bitmap, Shader.TileMode.CLAMP,Shader.TileMode.CLAMP);
paint.setShader(shader);
matrix.reset();
matrix.postScale(2f, 2f, zoomPos.x-10, zoomPos.y+60);
paint.getShader().setLocalMatrix(matrix);
int width = c.Width;
int height = c.Height;
float leftX = zoomPos.x - ((width * 100) / 1280);
float topY = zoomPos.y - ((height * 250) / 720);
float rightX = zoomPos.x + ((width * 100) / 1280);
float bottomY = zoomPos.y - ((height * 100) / 720);
canvas.drawRect(leftX , topY, rightX, bottomY, paint);
}
}
}
A ViewGroup draws its child Views in the dispatchDraw() method. We just need to move the magnifier drawing to after that happens.
The fix is simple. Move everything after the super call in onDraw() to after the super call in dispatchDraw().
...
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// Removed
}
#Override
protected void dispatchDraw(Canvas canvas) {
super.dispatchDraw(canvas);
if (!zooming) {
buildDrawingCache();
}
else {
bitmap = getDrawingCache();
shader = new BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
paint.setShader(shader);
matrix.reset();
matrix.postScale(2f, 2f, zoomPos.x - 10, zoomPos.y + 60);
paint.getShader().setLocalMatrix(matrix);
int width = c.Width;
int height = c.Height;
float leftX = zoomPos.x - ((width * 100) / 1280);
float topY = zoomPos.y - ((height * 250) / 720);
float rightX = zoomPos.x + ((width * 100) / 1280);
float bottomY = zoomPos.y - ((height * 100) / 720);
canvas.drawRect(leftX , topY, rightX, bottomY, paint);
}
}
...
You can just remove the onDraw() override, if you no longer need it for anything else.

Oval path Animation using image

I am trying to implement Oval path animation, I want to show path animation using image, I tried https://github.com/matthewrkula/AnimatedPathView but it's not work for oval. I also tried below code for oval path but it is shows circle, Anyone have an idea? Thanks in advance!!!
MyAnimation.java
public class MyAnimation extends Animation {
private View view;
private float cx, cy; // center x,y position of circular path
private float prevX, prevY; // previous x,y position of image during animation
private float r; // radius of circle
private float prevDx, prevDy;
/**
* #param view - View that will be animated
* #param r - radius of circular path
*/
public MyAnimation(View view, float r){
this.view = view;
this.r = r;
}
#Override
public boolean willChangeBounds() {
return true;
}
#Override
public void initialize(int width, int height, int parentWidth, int parentHeight) {
// calculate position of image center
int cxImage = width / 2;
int cyImage = height / 1;
cx = view.getLeft() + cxImage;
cy = view.getTop() + cyImage;
// set previous position to center
prevX = cx;
prevY = cy;
}
#Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
if(interpolatedTime == 0){
t.getMatrix().setTranslate(prevDx, prevDy);
return;
}
float angleDeg = (interpolatedTime * 360f + 90) % 360;
float angleRad = (float) Math.toRadians(angleDeg);
// r = radius, cx and cy = center point, a = angle (radians)
float x = (float) (cx + r * Math.cos(angleRad));
float y = (float) (cy + r * Math.sin(angleRad));
float dx = prevX - x;
float dy = prevY - y;
prevX = x;
prevY = y;
prevDx = dx;
prevDy = dy;
t.getMatrix().setTranslate(dx, dy);
}
}
PathAnimation.java
image = (ImageView) findViewById(R.id.image);
image.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Animation anim = new MyAnimation(image, 300);
anim.setDuration(1000);
image.startAnimation(anim);
}
});
I have found the solution after many tried using this custom class
AnimationView.java
public class AnimationView extends View {
Paint paint;
long animationDuration = 10000;
int framesPerSecond = 60;
Bitmap bm;
int bm_offsetX, bm_offsetY;
Path animPath;
PathMeasure pathMeasure;
float pathLength;
float step; //distance each step
float distance; //distance moved
float[] pos;
float[] tan;
Matrix matrix;
public AnimationView(Context context) {
super(context);
initMyView();
}
public AnimationView(Context context, AttributeSet attrs) {
super(context, attrs);
initMyView();
}
public AnimationView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initMyView();
}
#TargetApi(Build.VERSION_CODES.LOLLIPOP)
public void initMyView(){
paint = new Paint();
paint.setColor(Color.RED);
paint.setStrokeWidth(5);
paint.setStyle(Paint.Style.STROKE);
bm = BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher);
bm_offsetX = bm.getWidth()/2;
bm_offsetY = bm.getHeight()/2;
animPath = new Path();
animPath.moveTo(100, 100);
animPath.addArc(new RectF(1, 100, 300, 600), 1, 800);
animPath.close();
pathMeasure = new PathMeasure(animPath, false);
pathLength = pathMeasure.getLength();
Toast.makeText(getContext(), "pathLength: " + pathLength, Toast.LENGTH_LONG).show();
step = 1;
distance = 0;
pos = new float[2];
tan = new float[2];
matrix = new Matrix();
}
#Override
protected void onDraw(Canvas canvas) {
canvas.drawPath(animPath, paint);
if(distance < pathLength){
pathMeasure.getPosTan(distance, pos, tan);
matrix.reset();
float degrees = (float)(Math.atan2(tan[1], tan[0])*180.0/Math.PI);
matrix.postRotate(degrees, bm_offsetX, bm_offsetY);
matrix.postTranslate(pos[0]-bm_offsetX, pos[1]-bm_offsetY);
canvas.drawBitmap(bm, matrix, null);
distance += step;
}else{
distance = 0;
}
invalidate();
}
}
and put into xml
<com.example.android.mydemo.animation.pathanimation.AnimationView
android:layout_width="match_parent"
android:layout_height="450dp" />

Custom shape button

I am trying to make each slice of the pie a button. The pie is a bunch of vector drawables in an image view. I don't necessarily need the actual pie slices to be clicked. I was thinking of using Path to draw a transparent shape and place it on top and make that the button, but from what I understand, drawables aren't clickable.
I read one blog post that apparently used paths to make a custom shaped image view, and I know image views are clickable, but it seems like with the implementation in the blog post the image views are still rectangular, but the bitmaps the blogger was using in the example were just trimmed to a custom shape inside the image view. This is the post I'm referring to: http://www.androidhub4you.com/2014/10/android-custom-shape-imageview-rounded.html
Please explain this to me like a five year old. I'm relatively new to programming. Were it not for Android Studio's automatic everything, I would not be here.
Thank you.
You can just using drawArc and drawCircle to draw a radial menu, and using distance between touch point and center point and angle to detect which slice is currently being click. I wrote a Sample for you:
public class RadioButtons extends View {
//the number of slice
private int mSlices = 6;
//the angle of each slice
private int degreeStep = 360 / mSlices;
private int quarterDegreeMinus = -90;
private float mOuterRadius;
private float mInnerRadius;
//using radius square to prevent square root calculation
private float outerRadiusSquare;
private float innerRadiusSquare;
private Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
private RectF mSliceOval = new RectF();
private static final double quarterCircle = Math.PI / 2;
private float innerRadiusRatio = 0.3F;
//color for your slice
private int[] colors = new int[]{Color.GREEN, Color.GRAY, Color.BLUE, Color.CYAN, Color.DKGRAY, Color.RED};
private int mCenterX;
private int mCenterY;
private OnSliceClickListener mOnSliceClickListener;
private int mTouchSlop;
private boolean mPressed;
private float mLatestDownX;
private float mLatestDownY;
public interface OnSliceClickListener{
void onSlickClick(int slicePosition);
}
public RadioButtons(Context context){
this(context, null);
}
public RadioButtons(Context context, AttributeSet attrs){
this(context, attrs, 0);
}
public RadioButtons(Context context, AttributeSet attrs, int defStyle){
super(context, attrs, defStyle);
ViewConfiguration viewConfiguration = ViewConfiguration.get(context);
mTouchSlop = viewConfiguration.getScaledTouchSlop();
mPaint.setStrokeWidth(10);
}
public void setOnSliceClickListener(OnSliceClickListener onSliceClickListener){
mOnSliceClickListener = onSliceClickListener;
}
#Override
public void onSizeChanged(int w, int h, int oldw, int oldh){
mCenterX = w / 2;
mCenterY = h / 2;
mOuterRadius = mCenterX > mCenterY ? mCenterY : mCenterX;
mInnerRadius = mOuterRadius * innerRadiusRatio;
outerRadiusSquare = mOuterRadius * mOuterRadius;
innerRadiusSquare = mInnerRadius * mInnerRadius;
mSliceOval.left = mCenterX - mOuterRadius;
mSliceOval.right = mCenterX + mOuterRadius;
mSliceOval.top = mCenterY - mOuterRadius;
mSliceOval.bottom = mCenterY + mOuterRadius;
}
#Override
public boolean onTouchEvent(MotionEvent event){
float currX = event.getX();
float currY = event.getY();
switch(event.getActionMasked()){
case MotionEvent.ACTION_DOWN:
mLatestDownX = currX;
mLatestDownY = currY;
mPressed = true;
break;
case MotionEvent.ACTION_MOVE:
if(Math.abs(currX - mLatestDownX) > mTouchSlop || Math.abs(currY - mLatestDownY) > mTouchSlop) mPressed = false;
break;
case MotionEvent.ACTION_UP:
if(mPressed){
int dx = (int) currX - mCenterX;
int dy = (int) currY - mCenterY;
int distanceSquare = dx * dx + dy * dy;
//if the distance between touchpoint and centerpoint is smaller than outerRadius and longer than innerRadius, then we're in the clickable area
if(distanceSquare > innerRadiusSquare && distanceSquare < outerRadiusSquare){
//get the angle to detect which slice is currently being click
double angle = Math.atan2(dy, dx);
if(angle >= -quarterCircle && angle < 0){
angle += quarterCircle;
}else if(angle >= -Math.PI && angle < -quarterCircle){
angle += Math.PI + Math.PI + quarterCircle;
}else if(angle >= 0 && angle < Math.PI){
angle += quarterCircle;
}
double rawSliceIndex = angle / (Math.PI * 2) * mSlices;
if(mOnSliceClickListener != null){
mOnSliceClickListener.onSlickClick((int) rawSliceIndex);
}
}
}
break;
}
return true;
}
#Override
public void onDraw(Canvas canvas){
int startAngle = quarterDegreeMinus;
//draw slice
for(int i = 0; i < mSlices; i++){
mPaint.setStyle(Paint.Style.FILL);
mPaint.setColor(colors[i % colors.length]);
canvas.drawArc(mSliceOval, startAngle, degreeStep, true, mPaint);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setColor(Color.WHITE);
canvas.drawArc(mSliceOval, startAngle, degreeStep, true, mPaint);
startAngle += degreeStep;
}
//draw center circle
mPaint.setStyle(Paint.Style.FILL);
mPaint.setColor(Color.BLACK);
canvas.drawCircle(mCenterX, mCenterY, mInnerRadius, mPaint);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setColor(Color.WHITE);
canvas.drawCircle(mCenterX, mCenterY, mInnerRadius, mPaint);
}
}

Android : Zoom by center pinch

So, I have ScrollView, HorizantalScrollView and BoardView for TicTacToe game.
When user zooms, while i redraw scale into categories of cells through ScalegestureDetector zoom pinch is allocated on top, left of the screen, not at the centre of pinches, how can I allocate it to the center?
here is my project in github: https://github.com/boyfox/TestTicTac.git
Project used com.android.support:appcompat-v7
Anybody have a solution to this problem?
I think that you will want to move to an implementation closer to the Android Dragging and Scaling examples (and to the similar question here).
This is the start of what you need. You can pan the view as before and now scale the view at the center of the pinch gesture. It is your base BoardView with logic to draw points on clicks from here. It should be pretty easy to draw your custom X and O icons instead of circles.
BoardView.java
public class BoardView extends View {
private static final int JUST_SCALED_DURATION = 100;
private static final int MAX_TOUCH_DURATION = 1000;
private static final int MAX_TOUCH_DISTANCE = 10;
private boolean stayedWithinTouchDistance;
private float firstTouchX, firstTouchY;
private long firstTouchTime, lastScaleTime;
private float posX, posY, lastTouchX, lastTouchY;
private ScaleGestureDetector scaleDetector;
private float minScaleFactor = 0.1f;
private float maxScaleFactor = 5.0f;
private float scaleFactor = 1.0f;
private float cellSize = 50.0f;
private int numCells = 50;
private ArrayList<Point> points;
private Paint linePaint, pointPaint;
public BoardView(Context context) {
this(context, null, 0);
}
public BoardView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public BoardView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
points = new ArrayList<>();
linePaint = new Paint();
linePaint.setAntiAlias(true);
linePaint.setColor(-65536);
linePaint.setStrokeWidth(1.0f);
pointPaint = new Paint();
pointPaint.setAntiAlias(true);
pointPaint.setColor(-65536);
pointPaint.setStrokeWidth(1.0f);
posX = linePaint.getStrokeWidth();
posY = linePaint.getStrokeWidth();
scaleDetector = new ScaleGestureDetector(context, new ScaleListener());
}
private static float distancePx(float x1, float y1, float x2, float y2) {
float dx = x1 - x2;
float dy = y1 - y2;
return (float) Math.sqrt(dx * dx + dy * dy);
}
private float distanceDp(float distancePx) {
return distancePx / getResources().getDisplayMetrics().density;
}
private Point coerceToGrid(Point point) {
point.x = (int) ((((int) (point.x / cellSize)) * cellSize) + (cellSize / 2));
point.y = (int) ((((int) (point.y / cellSize)) * cellSize) + (cellSize / 2));
return point;
}
#Override
public boolean onTouchEvent(#NonNull MotionEvent event) {
scaleDetector.onTouchEvent(event);
final int action = MotionEventCompat.getActionMasked(event);
if (action == MotionEvent.ACTION_DOWN) {
stayedWithinTouchDistance = true;
firstTouchX = lastTouchX = event.getRawX();
firstTouchY = lastTouchY = event.getRawY();
firstTouchTime = System.currentTimeMillis();
} else if (action == MotionEvent.ACTION_MOVE) {
float thisTouchX = event.getRawX();
float thisTouchY = event.getRawY();
boolean justScaled = System.currentTimeMillis() - lastScaleTime < JUST_SCALED_DURATION;
float distancePx = distancePx(firstTouchX, firstTouchY, thisTouchX, thisTouchY);
stayedWithinTouchDistance = stayedWithinTouchDistance &&
distanceDp(distancePx) < MAX_TOUCH_DISTANCE;
if (!stayedWithinTouchDistance && !scaleDetector.isInProgress() && !justScaled) {
posX += thisTouchX - lastTouchX;
posY += thisTouchY - lastTouchY;
invalidate();
}
lastTouchX = thisTouchX;
lastTouchY = thisTouchY;
} else if (action == MotionEvent.ACTION_UP) {
long touchDuration = System.currentTimeMillis() - firstTouchTime;
if (touchDuration < MAX_TOUCH_DURATION && stayedWithinTouchDistance) {
int[] location = {0, 0};
getLocationOnScreen(location);
float x = ((lastTouchX - posX - location[0]) / scaleFactor);
float y = ((lastTouchY - posY - location[1]) / scaleFactor);
points.add(coerceToGrid(new Point((int) x, (int) y)));
invalidate();
}
}
return true;
}
private class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener {
#Override
public boolean onScale(ScaleGestureDetector detector) {
lastScaleTime = System.currentTimeMillis();
float scale = detector.getScaleFactor();
scaleFactor = Math.max(minScaleFactor, Math.min(scaleFactor * scale, maxScaleFactor));
if (scaleFactor > minScaleFactor && scaleFactor < maxScaleFactor) {
float centerX = detector.getFocusX();
float centerY = detector.getFocusY();
float diffX = centerX - posX;
float diffY = centerY - posY;
diffX = diffX * scale - diffX;
diffY = diffY * scale - diffY;
posX -= diffX;
posY -= diffY;
invalidate();
return true;
}
return false;
}
}
private float getScaledCellSize() {
return scaleFactor * cellSize;
}
private float getScaledBoardSize() {
return numCells * getScaledCellSize();
}
private void drawBoard(Canvas canvas) {
for (int i = 0; i <= numCells; i++) {
float total = getScaledBoardSize();
float offset = getScaledCellSize() * i;
canvas.drawLine(offset, 0, offset, total, linePaint);
canvas.drawLine(0, offset, total, offset, linePaint);
}
}
private void drawPoints(Canvas canvas) {
for (Point point : points) {
float x = point.x * scaleFactor;
float y = point.y * scaleFactor;
float r = getScaledCellSize() / 4;
canvas.drawCircle(x, y, r, pointPaint);
}
}
#Override
public void onDraw(Canvas canvas) {
super.onDraw(canvas);
float total = getScaledBoardSize();
float edge = linePaint.getStrokeWidth();
posX = Math.max(Math.min(edge, getWidth() - total - edge), Math.min(edge, posX));
posY = Math.max(Math.min(edge, getHeight() - total - edge), Math.min(edge, posY));
canvas.save();
canvas.translate(posX, posY);
drawBoard(canvas);
drawPoints(canvas);
canvas.restore();
}
}
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.example.client.BoardView
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</FrameLayout>

Categories

Resources