247 lines
7.4 KiB
Java
247 lines
7.4 KiB
Java
package app.fedilab.android.mastodon.helper;
|
|
|
|
import android.content.Context;
|
|
import android.graphics.Canvas;
|
|
import android.graphics.Paint;
|
|
import android.graphics.Rect;
|
|
import android.util.AttributeSet;
|
|
import android.util.SparseArray;
|
|
import android.view.MotionEvent;
|
|
import android.view.View;
|
|
|
|
import java.util.HashSet;
|
|
import java.util.Random;
|
|
|
|
import app.fedilab.android.R;
|
|
|
|
//Original work at https://stackoverflow.com/a/17830245
|
|
public class CirclesDrawingView extends View {
|
|
|
|
|
|
// Radius limit in pixels
|
|
private final static int RADIUS_LIMIT = 100;
|
|
private static final int CIRCLES_LIMIT = 1;
|
|
private final Random mRadiusGenerator = new Random();
|
|
/**
|
|
* All available circles
|
|
*/
|
|
private final HashSet<CircleArea> mCircles = new HashSet<>(CIRCLES_LIMIT);
|
|
private final SparseArray<CircleArea> mCirclePointer = new SparseArray<>(CIRCLES_LIMIT);
|
|
/**
|
|
* Paint to draw circles
|
|
*/
|
|
private Paint mCirclePaint;
|
|
private CircleArea touchedCircle;
|
|
|
|
/**
|
|
* Default constructor
|
|
*
|
|
* @param ct {@link android.content.Context}
|
|
*/
|
|
public CirclesDrawingView(final Context ct) {
|
|
super(ct);
|
|
init(ct);
|
|
}
|
|
|
|
public CirclesDrawingView(final Context ct, final AttributeSet attrs) {
|
|
super(ct, attrs);
|
|
init(ct);
|
|
}
|
|
|
|
public CirclesDrawingView(final Context ct, final AttributeSet attrs, final int defStyle) {
|
|
super(ct, attrs, defStyle);
|
|
init(ct);
|
|
}
|
|
|
|
public CircleArea getTouchedCircle() {
|
|
return this.touchedCircle;
|
|
}
|
|
|
|
private void init(final Context ct) {
|
|
// Generate bitmap used for background
|
|
mCirclePaint = new Paint();
|
|
|
|
mCirclePaint.setColor(ThemeHelper.getAttColor(ct, R.attr.colorPrimary));
|
|
mCirclePaint.setStrokeWidth(10);
|
|
mCirclePaint.setStyle(Paint.Style.STROKE);
|
|
}
|
|
|
|
@Override
|
|
public void onDraw(final Canvas canv) {
|
|
// background bitmap to cover all area
|
|
for (CircleArea circle : mCircles) {
|
|
canv.drawCircle(circle.centerX, circle.centerY, circle.radius, mCirclePaint);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public boolean onTouchEvent(final MotionEvent event) {
|
|
boolean handled = false;
|
|
|
|
|
|
int xTouch;
|
|
int yTouch;
|
|
int pointerId;
|
|
int actionIndex = event.getActionIndex();
|
|
|
|
// get touch event coordinates and make transparent circle from it
|
|
switch (event.getActionMasked()) {
|
|
case MotionEvent.ACTION_DOWN:
|
|
// it's the first pointer, so clear all existing pointers data
|
|
clearCirclePointer();
|
|
|
|
xTouch = (int) event.getX(0);
|
|
yTouch = (int) event.getY(0);
|
|
|
|
// check if we've touched inside some circle
|
|
touchedCircle = obtainTouchedCircle(xTouch, yTouch);
|
|
touchedCircle.centerX = xTouch;
|
|
touchedCircle.centerY = yTouch;
|
|
mCirclePointer.put(event.getPointerId(0), touchedCircle);
|
|
invalidate();
|
|
handled = true;
|
|
break;
|
|
|
|
case MotionEvent.ACTION_POINTER_DOWN:
|
|
// It secondary pointers, so obtain their ids and check circles
|
|
pointerId = event.getPointerId(actionIndex);
|
|
|
|
xTouch = (int) event.getX(actionIndex);
|
|
yTouch = (int) event.getY(actionIndex);
|
|
|
|
// check if we've touched inside some circle
|
|
touchedCircle = obtainTouchedCircle(xTouch, yTouch);
|
|
|
|
mCirclePointer.put(pointerId, touchedCircle);
|
|
touchedCircle.centerX = xTouch;
|
|
touchedCircle.centerY = yTouch;
|
|
invalidate();
|
|
handled = true;
|
|
break;
|
|
|
|
case MotionEvent.ACTION_MOVE:
|
|
final int pointerCount = event.getPointerCount();
|
|
|
|
for (actionIndex = 0; actionIndex < pointerCount; actionIndex++) {
|
|
// Some pointer has moved, search it by pointer id
|
|
pointerId = event.getPointerId(actionIndex);
|
|
|
|
xTouch = (int) event.getX(actionIndex);
|
|
yTouch = (int) event.getY(actionIndex);
|
|
|
|
touchedCircle = mCirclePointer.get(pointerId);
|
|
|
|
if (null != touchedCircle) {
|
|
touchedCircle.centerX = xTouch;
|
|
touchedCircle.centerY = yTouch;
|
|
}
|
|
}
|
|
invalidate();
|
|
handled = true;
|
|
break;
|
|
|
|
case MotionEvent.ACTION_UP:
|
|
clearCirclePointer();
|
|
invalidate();
|
|
handled = true;
|
|
break;
|
|
|
|
case MotionEvent.ACTION_POINTER_UP:
|
|
// not general pointer was up
|
|
pointerId = event.getPointerId(actionIndex);
|
|
|
|
mCirclePointer.remove(pointerId);
|
|
invalidate();
|
|
handled = true;
|
|
break;
|
|
|
|
case MotionEvent.ACTION_CANCEL:
|
|
handled = true;
|
|
break;
|
|
|
|
default:
|
|
// do nothing
|
|
break;
|
|
}
|
|
|
|
return super.onTouchEvent(event) || handled;
|
|
}
|
|
|
|
/**
|
|
* Clears all CircleArea - pointer id relations
|
|
*/
|
|
private void clearCirclePointer() {
|
|
|
|
mCirclePointer.clear();
|
|
}
|
|
|
|
/**
|
|
* Search and creates new (if needed) circle based on touch area
|
|
*
|
|
* @param xTouch int x of touch
|
|
* @param yTouch int y of touch
|
|
* @return obtained {@link CircleArea}
|
|
*/
|
|
private CircleArea obtainTouchedCircle(final int xTouch, final int yTouch) {
|
|
CircleArea touchedCircle = getTouchedCircle(xTouch, yTouch);
|
|
|
|
if (null == touchedCircle) {
|
|
touchedCircle = new CircleArea(xTouch, yTouch, mRadiusGenerator.nextInt(RADIUS_LIMIT) + RADIUS_LIMIT);
|
|
|
|
if (mCircles.size() == CIRCLES_LIMIT) {
|
|
// remove first circle
|
|
mCircles.clear();
|
|
}
|
|
|
|
mCircles.add(touchedCircle);
|
|
}
|
|
|
|
return touchedCircle;
|
|
}
|
|
|
|
/**
|
|
* Determines touched circle
|
|
*
|
|
* @param xTouch int x touch coordinate
|
|
* @param yTouch int y touch coordinate
|
|
* @return {@link CircleArea} touched circle or null if no circle has been touched
|
|
*/
|
|
private CircleArea getTouchedCircle(final int xTouch, final int yTouch) {
|
|
CircleArea touched = null;
|
|
|
|
for (CircleArea circle : mCircles) {
|
|
if ((circle.centerX - xTouch) * (circle.centerX - xTouch) + (circle.centerY - yTouch) * (circle.centerY - yTouch) <= circle.radius * circle.radius) {
|
|
touched = circle;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return touched;
|
|
}
|
|
|
|
@Override
|
|
protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) {
|
|
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
|
|
new Rect(0, 0, getMeasuredWidth(), getMeasuredHeight());
|
|
}
|
|
|
|
/**
|
|
* Stores data about single circle
|
|
*/
|
|
public static class CircleArea {
|
|
public int centerX;
|
|
public int centerY;
|
|
int radius;
|
|
|
|
CircleArea(int centerX, int centerY, int radius) {
|
|
this.radius = radius;
|
|
this.centerX = centerX;
|
|
this.centerY = centerY;
|
|
}
|
|
|
|
@Override
|
|
public String toString() {
|
|
return "Circle[" + centerX + ", " + centerY + ", " + radius + "]";
|
|
}
|
|
}
|
|
} |