Android scale ImageView using anchor points - java

The goal is: the Imageview have 4 squares on each corners. If the user touch and drag one of this, the image is scaled.
Given the image, the 4 squares and the touched square how can i implement this feature? (square = Rectf class)
example of image

I have find out a possible solution, if anyone need the code:
float centerX, centerY, startScale, startX, startY;
private void startScaling(MotionEvent e) {
ImageView imageView = (ImageView) getChildAt(0);
switch (e.getAction()) {
case MotionEvent.ACTION_DOWN:
startX = e.getX();
startY = e.getY();
startScale = imageView.getScaleX();
centerX = imageView.getX() + imageView.getWidth() / 2F;
centerY = imageView.getY() + imageView.getHeight() / 2F;
break;
case MotionEvent.ACTION_MOVE:
// euclidean distance
double length1 = Point.distance(centerX, centerY, startX, startY);
double length2 = Point.distance(centerX, centerY, e.getX(), e.getY());
if(length2 > length1) {
//scale up
float scaleFactor = (float) (length2 / length1);
//scale the image
imageView.setScaleX(startScale * scaleFactor);
imageView.setScaleY(startScale * scaleFactor);
}else {
//scale down
// calculate the scale factor
float scaleFactor = (float) (length1 / length2);
// scale the image
imageView.setScaleX(startScale / scaleFactor);
imageView.setScaleY(startScale / scaleFactor);
}
float scaledWidth = imageView.getWidth() * imageView.getScaleX();
float scaledHeight = imageView.getHeight() * imageView.getScaleY();
// calculate new boubds of the image
float left = centerX - scaledWidth / 2F;
float top = centerY - scaledHeight / 2F;
float right = left + scaledWidth;
float bottom = top + scaledHeight;
break;
case MotionEvent.ACTION_UP:
break;
}
}

Related

create percentage square in java instead circles

I am working in a existing project for an amazfit watchface. Code is based in java. The question is: In original project, for show battery, steps and sport percentage, show three circles. My idea is to draw a rectangle (or a line) instead the original circle. The problem is I am new programming in java and I donĀ“t know for change this without FC app.
this watch has two screens: one active and other in stand-by mode (8colors only)
active mode draws circle, standby mode works with an png image.
This is the code (for circles):
package es.xxxx.xxxx.widget;
private final float startAngleBattery = 30;
private final float arcSizeBattery = 360 - startAngleBattery - startAngleBattery;
#Override
public void init(Service service) {
this.thickness = (int) service.getResources().getDimension(R.dimen.xxxx_circles_thickness);
this.textPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
this.textPaint.setTypeface(ResourceManager.getTypeFace(service.getResources(), ResourceManager.Font.BEBAS_NEUE));
this.textPaint.setTextSize(service.getResources().getDimension(R.dimen.xxxx_circles_font_size));
this.textPaint.setColor(service.getResources().getColor(R.color.xxxx_time_colour));
this.textPaint.setTextAlign(Paint.Align.CENTER);
this.ring = new Paint(Paint.ANTI_ALIAS_FLAG);
this.ring.setStrokeCap(Paint.Cap.ROUND);
this.ring.setStyle(Paint.Style.STROKE);
this.ring.setStrokeWidth(this.thickness);
this.circle = new Paint(Paint.ANTI_ALIAS_FLAG);
this.circle.setColor(Color.BLACK);
this.circle.setStrokeWidth(1f);
this.circle.setStyle(Paint.Style.STROKE);
#Override
public void draw(Canvas canvas, float width, float height, float centerX, float centerY) {
int count = canvas.save();
int radius = Math.round(Math.min(width / 2, height / 2)) - this.thickness;
RectF oval = new RectF(centerX - radius, centerY - radius, centerX + radius, centerY + radius);
// rotate from 0 to 270 degrees
canvas.rotate(90, centerX, centerY);
this.ring.setColor(this.backgroundColour);
canvas.drawArc(oval, startAngleBattery, arcSizeBattery, false, ring);
if (batterySweepAngle != null) {
float px = getPointX(oval, centerX, startAngleBattery, batterySweepAngle);
float py = getPointY(oval, centerY, startAngleBattery, batterySweepAngle);
this.ring.setColor(this.batteryColour);
canvas.drawArc(oval, startAngleBattery, batterySweepAngle, false, ring);
canvas.drawCircle(px, py, this.thickness / 3f, circle);
canvas.drawCircle(px, py, this.thickness / 6f, circle);
}
canvas.restoreToCount(count);
if (this.batteryData != null) {
String text = String.format("%02d", this.batteryData.getLevel() * 100 / this.batteryData.getScale());
canvas.drawText(text, batteryTextLeft, batteryTextTop, textPaint);
}
}
#Override
public void onDataUpdate(DataType type, Object value) {
switch (type) {
case BATTERY:
onBatteryData((Battery) value);
break;
}
}
#Override
public List<DataType> getDataTypes() {
return Arrays.asList(DataType.BATTERY);
private void onBatteryData(Battery battery) {
this.batteryData = battery;
if (batteryData == null) {
this.batterySweepAngle = 0f;
} else {
float scale = batteryData.getLevel() / (float) batteryData.getScale();
this.batterySweepAngle = Math.min(arcSizeBattery, arcSizeBattery * scale);
}
}
private RectF nextOval(RectF oval) {
oval.left = oval.left + this.thickness + MARGIN;
oval.top = oval.top + this.thickness + MARGIN;
oval.right = oval.right - this.thickness - MARGIN;
oval.bottom = oval.bottom - this.thickness - MARGIN;
return oval;
}
private float getPointX(RectF oval, float cx, float startAngle, float sweepAngle) {
float width = oval.right - oval.left;
return (float) (cx + (width / 2D) * Math.cos((sweepAngle + startAngle) * Math.PI / 180));
}
private float getPointY(RectF oval, float cy, float startAngle, float sweepAngle) {
float height = oval.bottom - oval.top;
return (float) (cy + (height / 2D) * Math.sin((sweepAngle + startAngle) * Math.PI / 180));
}
#Override
public List<SlptViewComponent> buildSlptViewComponent(Service service) {
Typeface timeTypeFace = ResourceManager.getTypeFace(service.getResources(), ResourceManager.Font.BEBAS_NEUE);
SlptLinearLayout power = new SlptLinearLayout();
power.alignX = 2;
power.alignY = 2;
power.add(new SlptPowerNumView());
power.setTextAttrForAll(
service.getResources().getDimension(R.dimen.xxxx_circles_font_size_slpt),
-1,
timeTypeFace
);
power.setStart(
(int) service.getResources().getDimension(R.dimen.xxxx_battery_text_left_slpt),
(int) service.getResources().getDimension(R.dimen.xxxx_battery_text_top_slpt));
SlptPowerArcAnglePicView powerArcView = new SlptPowerArcAnglePicView();
powerArcView.setImagePicture(Util.assetToBytes(service, "battery_splt.png"));
powerArcView.start_angle = (int) startAngleBattery + 180 - 3;
powerArcView.full_angle = (int) arcSizeBattery + 6;
return Arrays.asList(power, powerArcView);
}
}
Thanks in advance.
For anyone still searching...
You can draw the rectangular in the "draw" function that runs in loop constantly when screen is on, however, screen off (SLPT mode) uses ingenic's libraries to draw (function buildSlptViewComponent) and there is the real problem.
I don't want to get into details because it would be pages, so have a look at GreatFit project.

How to draw bitmap image on top in arc

I want to draw image on Top in each Arc of Canvas
private void drawImage(Canvas canvas, float tempAngle, Bitmap bitmap,String mValue) {
//get every arc img width and angle
int imgWidth = (radius / mWheelItems.size());
//int imgWidth = (radius / 3);
float angle = (float) ((tempAngle + 360 / mWheelItems.size() /2) * Math.PI / 180);
//calculate x and y
int x = (int) (center + radius / 2 / 2 * Math.cos(angle));
int y = (int) (center + radius / 2 / 2 * Math.sin(angle));
int top=y - imgWidth/2;
int bottom=y +imgWidth/2;
int left=x - imgWidth /2;
int right=x + imgWidth / 2;
Rect rect = new Rect(left, top, right, bottom);
final Rect rect1 = new Rect(x - imgWidth /2 , y - imgWidth / 2 , bitmap.getWidth() , bitmap.getHeight());
canvas.drawBitmap(bitmap, null, rect, null);
}
the Arc is made according to the size of items
The Result is shown like that
But I want the image bitmap shown on top like that of rect. Red also want to large size of images or bitmap
Solved by Specific Tab and Big Screen Tablets
Change the X and Y coordination According to Angle
if(tempAngle==0) {
x = x + 50;
}
if(tempAngle==60) {
y = y + 50;
}
if(tempAngle==120) {
imgWidth = imgWidth-12;
y = y + 20;
x = x - 40;
}
if(tempAngle==180) {
imgWidth = imgWidth+12;
x = x - 50;
}
if(tempAngle==240) {
y = y - 50;
}
if(tempAngle==300) {
y = y - 20;
x = x + 40;
}

Updating ImageView with a new Bitmap resets the matrix

I am trying to freely translate Bitmap inside an imageView.
I have ImageView with MATRIX scaletype and width and height match_parent
Here is my code for Touch listener
Following is my code for touch
public class Touch implements OnTouchListener
{
// These matrices will be used to move and zoom image
public Matrix matrix = new Matrix();
Matrix savedMatrix = new Matrix();
// We can be in one of these 3 states
static final int NONE = 0;
static final int DRAG = 1;
static final int ZOOM = 2;
int mode = NONE;
// Remember some things for zooming
PointF start = new PointF();
PointF mid = new PointF();
float oldDist = 1f;
float[] lastEvent = null;
float d = 0f;
float newRot = 0f;
private float dx; // postTranslate X distance
private float dy; // postTranslate Y distance
private float[] matrixValues = new float[9];
float matrixX = 0; // X coordinate of matrix inside the ImageView
float matrixY = 0; // Y coordinate of matrix inside the ImageView
float width = 0; // width of drawable
float height = 0; // height of drawable
View mainView;
#Override
public boolean onTouch(View v, MotionEvent event)
{
ImageView view = (ImageView) v;
switch (event.getAction() & MotionEvent.ACTION_MASK)
{
case MotionEvent.ACTION_DOWN:
savedMatrix.set(matrix);
start.set(event.getX(), event.getY());
mode = DRAG;
lastEvent = null;
break;
case MotionEvent.ACTION_POINTER_DOWN:
oldDist = spacing(event);
if (oldDist > 10f)
{
//savedMatrix.set(matrix);
//midPoint(mid, event);
//mode = ZOOM;
}
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_POINTER_UP:
mode = NONE;
lastEvent = null;
break;
case MotionEvent.ACTION_MOVE:
if (mode == DRAG)
{
matrix.set(savedMatrix);
matrix.getValues(matrixValues);
matrixX = matrixValues[2];
matrixY = matrixValues[5];
width = matrixValues[0] * (((ImageView) view).getDrawable()
.getIntrinsicWidth());
height = matrixValues[4] * (((ImageView) view).getDrawable()
.getIntrinsicHeight());
dx = event.getX() - start.x;
dy = event.getY() - start.y;
//if image will go outside left bound
if (matrixX + dx < 0){
dx = -matrixX;
}
//if image will go outside right bound
if(matrixX + dx + width > view.getWidth()){
dx = view.getWidth() - matrixX - width;
}
//if image will go oustside top bound
if (matrixY + dy < 0){
dy = -matrixY;
}
//if image will go outside bottom bound
if(matrixY + dy + height > view.getHeight()){
dy = view.getHeight() - matrixY - height;
}
matrix.postTranslate(dx, dy);
}
break;
}
view.setImageMatrix(matrix);
return true; // indicate event was handled
}
public void updateValues(ImageView view)
{
matrix = view.getImageMatrix();
matrix.postTranslate(dx, dy);
view.setImageMatrix(matrix);
}
/** Determine the space between the first two fingers */
#SuppressLint("FloatMath")
private float spacing(MotionEvent event) {
float x = event.getX(0) - event.getX(1);
float y = event.getY(0) - event.getY(1);
return FloatMath.sqrt(x * x + y * y);
}
}
It works perfect, however when i try to attach another Bitmap, it loses its current position and bitmap is moved to its original 0,0 state. However after setting bitmap if i call updateValues(ImageView view) it retains its current dragged positon. But when i try to drag again it suddenly goes to 0,0
How can i fix this. I want the new bitmap to start at the same position as before
Also how can i set the image to appear in Center at first place , currently it shows at top left corner
Try this to make it center on load
vTranslate.x = (getWidth()/2) - (scale * (sWidth()/2));
vTranslate.y = (getHeight()/2) - (scale * (sHeight()/2));

Custom View drawArc,detect user touch on draw path of arc

I am creating a custom view which is a kind of arc slider progress view.I can draw more or less of the arc based on where the user touches(on the x axis) by calculating the sweep, i do this by first calculating the percetage where the user touched along the x axis..0% would be all the way to the left and 100% would be all the way to the right.
I want to take this a step further, instead off drawing the arc based on the x coordinate that the user presses, I want to make it move only when the user touches on the actual arc draw path, so its more realistic. I am still new to custom views and my maths is limited but if I get some tips I would be grateful thanks
class ArcProgress extends View {
Context cx;
float width;
float height;
float center_x, center_y;
final RectF oval = new RectF();
final RectF touchArea = new RectF();
float sweep = 0;
float left, right;
int percent = 0;
public ArcProgress(Context context) {
super(context);
cx = context;
}
public int getPercentage() {
return percent;
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
setBackgroundColor(0xfff0ebde);
width = (float) getWidth();
height = (float) getHeight();
float radius;
if (width > height) {
radius = height / 3;
} else {
radius = width / 3;
}
Paint paint = new Paint();
paint.setAntiAlias(true);
paint.setColor(0xffd2c8b6);
paint.setStrokeWidth(35);
paint.setStyle(Paint.Style.STROKE);
center_x = width / 2;
center_y = height / 2;
left = center_x - radius;
float top = center_y - radius;
right = center_x + radius;
float bottom = center_y + radius;
oval.set(left, top, right, bottom);
//this is the background arc, it remains constant
canvas.drawArc(oval, 180, 180, false, paint);
paint.setStrokeWidth(10);
paint.setColor(0xffe0524d);
//this is the red arc whichhas its sweep argument manipulated by on touch
canvas.drawArc(oval, 180, sweep, false, paint);
}
#Override
public boolean onTouchEvent(MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_MOVE) {
float xPosition = event.getX();
float yPosition = event.getY();
if (oval.contains(xPosition, yPosition)) {
float x = xPosition - left;
float s = x * 100;
float b = s / oval.width();
percent = Math.round(b);
sweep = (180 / 100.0f) * (float) percent;
invalidate();
} else {
if (xPosition < left) {
percent = 0;
sweep = (180 / 100.0f) * (float) percent;
invalidate();
}
if (xPosition > right) {
percent = 100;
sweep = (180 / 100.0f) * (float) percent;
invalidate();
}
}
}
return true;
}
}
I want to make it move only when the user touches on the actual arc
draw path
At the beginning of onTouchEvent() you need to check whether xPosition and yPosition are fulfilling some condition. If yes, you do the stuff, which you are doing now. If no, return true.
Condition:
We want to check whether x, y are in that grey arc background:
Let's calculate a distance from (x, y) to that point (a, b) in the center:
final dist = distance(x, y, a, b)
distance() is a simple Euclidean distance between points (x,y) and (a,b):
double distance(int x, int y, int a, int b)
{
return Math.sqrt((x - a) * (x - a) + (y - b) * (y - b));
}
x, y are in that grey arc background, if y > Y && dist >= r && dist <= R.
Does this work for you?
You don't need a lot of Maths. You can calculate the distance of the touch point from the center of your arc (it's a circle so it's easy) and the compare that with the radius you are using. That will tell you if the point is on the arc (almost, see below for full case).
Point touchEv = ...;
Point circleCenter = ...;
//the radius of the circle you used to draw the arc
float circleRadius = ...;
//how far from the arc should a touch point treated as it's on the arc
float maxDiff = getResources().getDimension(R.dimen.max_diff_dp);
//calculate the distance of the touch point from the center of your circle
float dist = Math.pow(touchEv.x-circleCenter.x,2) + Math.pow(touchEv.y- circleCenter.y,2)
dist = Math.sqrt(dist);
//We also need the bounding rect of the top half of the circle (the visible arc)
Rect topBoundingRect = new Rect(circleCenter.x - circleRadius,
circleCenter.y - circleRadius,
circleCenter.x + circleRadius,
circleCenter.y);
if (Math.abs(dist - circleRadius) <= maxDiff &&
topBoundingRect.contains(touchEv.x, touchEv.y)) {
// the user is touching the arc
}

Drawing portion of bitmap in Android?

I have here a method to draw a rotated scaled bitmap in Android:
public void drawRotatedScaledBitmap(Bitmap b,
float centerX, float centerY, float width, float height, float angle)
{
float scaleX = width / b.getWidth();
float scaleY = height / b.getHeight();
centerX -= (b.getWidth() * scaleX) / 2.0f;
centerY -= (b.getHeight() * scaleY) / 2.0f;
matrix.reset();
matrix.setTranslate(centerX, centerY);
matrix.postRotate(angle * (180.0f / (float)(Math.PI)),
centerX + (b.getWidth() * scaleX) / 2.0f,
centerY + (b.getHeight() * scaleY) / 2.0f);
matrix.preScale(scaleX,scaleY);
canvas.drawBitmap(b, matrix, null);
}
I'm not sure how I could modify this to take in float sourceX, float sourceY, float sourceW, float sourceH.
I want to render tile sets so I need to tell it say 64,64,64,64 on the bitmap.
How could I do this?
Thanks
Maybe you can try canvas.clipRect() to specify the drawing area.
Here is how I solved it:
public void drawRotatedScaledBitmap(Bitmap b,
float centerX, float centerY, float width, float height,
float sourceX, float sourceY,
float sourceWidth, float sourceHeight, float angle)
{
float cx = centerX;
float cy = centerY;
dstRect.left = (int)centerX - (int)(width / 2);
dstRect.top = (int)centerY - (int)(height / 2);
dstRect.right = dstRect.left + (int)width;
dstRect.bottom = dstRect.top + (int)height;
srcRect.left = (int)sourceX;
srcRect.top = (int)sourceY;
srcRect.right = srcRect.left + (int)sourceWidth;
srcRect.bottom = srcRect.top + (int)sourceHeight;
canvas.rotate(angle * (180.0f / (float)(Math.PI)),cx,cy);
canvas.drawBitmap(b, srcRect, dstRect, null);
canvas.rotate(-angle * (180.0f / (float)(Math.PI)),cx,cy);
}

Categories