I am trying to implement TapToFocus feature using the CameraX Api .I have implemented it successfully but not able to know how to draw a circle on the preview describing the Location pressed by the user .
I want to have a circle in the preview like the image has
The are many ways to draw and animate a focus ring around a tap position on PreviewView, some fancier than others. One simple way of doing so is to:
Create a Drawable of the ring, something like this for instance.
Layout an ImageView containing the Drawable on top of PreviewView, and initially hide it.
<FrameLayout ...>
<androidx.camera.view.PreviewView
... />
<ImageView
android:id="#+id/focusRing"
android:src="#drawable/focus_ring"
android:visibility="invisible"
... />
</FrameLayout>
Set up a touch listener on PreviewView. On a touch event, use the event's coordinates to show the ring around it.
private void animateFocusRing(float x, float y) {
ImageView focusRing = findViewById(R.id.focusRing);
// Move the focus ring so that its center is at the tap location (x, y)
float width = focusRing.getWidth();
float height = focusRing.getHeight();
focusRing.setX(x - width / 2);
focusRing.setY(y - height / 2);
// Show focus ring
focusRing.setVisibility(View.VISIBLE);
focusRing.setAlpha(1F);
// Animate the focus ring to disappear
focusRing.animate()
.setStartDelay(500)
.setDuration(300)
.alpha(0F)
.setListener(new Animator.AnimatorListener() {
#Override
public void onAnimationEnd(Animator animator) {
focusRing.setVisibility(View.INVISIBLE);
}
// The rest of AnimatorListener's methods.
});
}
Related
I'm trying to implement a circular reveal animation which consist to reveal the view according the user finger when he's touching the view.
First, my view is inside an infinite circular reveal loop to keep the rounded aspect as long as the user do nothing.
Then when the user touch the view and start to swipe I calculated a factor inside the ACTION_MOVE to increase the radius in real time and call the circular reveal at each pass.
public void reveal(final View view,
final float centerX,
final float centerY,
final float startRadius,
final float endRadius,
final int duration) {
anim = ViewAnimationUtils.createCircularReveal(view, (int)centerX, (int)centerY, (int)startRadius, (int)endRadius);
anim.setDuration(duration);
anim.addListener(new Animator.AnimatorListener() {
#Override
public void onAnimationStart(Animator animation) {
}
#Override
public void onAnimationEnd(Animator animation) {
if (isRepeating) {
reveal(view,centerX,centerY,startRadius,endRadius,duration);
}
}
#Override
public void onAnimationCancel(Animator animation) {
}
#Override
public void onAnimationRepeat(Animator animation) {
}
});
anim.start();
}
I recall the circular reveal method again inside its Listener to create the loop and the most important to avoid visual distortion.
I have noticed that if I didn't do this way some very fast distortion showed up because the animation duration is faster than each pass in ACTION_MOVE so the anim have time to finish before the next pass.
The distortion is the representation of the missing part of the view when the animation is finished and came back very fast to the current radius set with the Touch like a flash.
So this way with the method recall inside the Listener is working great until there.
Problem : On one of my devices there are senseless distortion in all directions while swiping.
According to my logs it's not a radius distortion because the start and end radius is always the same after each pass so the radius should not be moving like this especially when the finger is stopped, and the animation never stop thanks to the Listener like I explained above.
What am I doing wrong ?
I searched a lot and didn't find the same problem so I tried other ways like drawing canvas but unfortunately it doesn't match with my needs.
Is there another way to achieve this kind of animation even completely differently ?
Any lead is appreciate thanks in advance
EDIT : inside the ACTION_MOVE
scaleRadius = (float) (2 * distance / maxWidth + startScale);
if (scaleRadius < 1) {
scaleRadius = 1;
}
animator.reveal(this, middleCircleX, middleCircleY,
initialCircleRadius*scaleRadius, initialCircleRadius*scaleRadius, 0);
Logs : Always the same factor then the radius should not move because my finger is stopped
2020-03-27 15:42:56.113 onTouch: scaleRadius 1.7870371
2020-03-27 15:42:56.113 onTouch: scaleRadius 1.7870373
2020-03-27 15:42:56.227 onTouch: scaleRadius 1.7870371
2020-03-27 15:42:56.227 onTouch: scaleRadius 1.7870373
2020-03-27 15:42:56.331 onTouch: scaleRadius 1.7870374
2020-03-27 15:42:56.331 onTouch: scaleRadius 1.7870371
I am assuming that you dont want the flickering animation but rather that the radius of your circe transitions smoothly and becomes either bigger or smaller as a function of how far you swipe on the screen. (please correct me if this assumption is wrong)
I will assume that centerX and centerY are always the same value and are basically global variables.
Create a couple of additional global variables:
boolean animIsCurrentlyPlaying=false;
float mCurrentRadius=initialCircleSize; //i guess this is 1 in your case
float mTargetRadius;
Now on the ACTION_MOVE event you will do the following:
1- set mTargetRadius to be some function of the distance between centerX and centerY and the position of the finger on the screen (euclidean distance might be a good pick here is an example);
mTargetRadius=Float.valueOf(Math.sqrt((double)(Math.pow(event.getX()-centerX,2)+Math.pow(event.getY()-centerY,2))));
2- excecute the following method revealModified(view, 100);
public void revealModified(final View view,
final int duration) {
if(!animIsCurrentlyPlaying){
//here i just reference the global vars
anim = ViewAnimationUtils.createCircularReveal(view, centerX, centerY, mCurrentRadius, mTargetRadius);
mCurrentRadius=mTargetRadius;
anim.setDuration(duration);
anim.addListener(new Animator.AnimatorListener() {
#Override
public void onAnimationEnd(Animator animation) {
animIsCurrentlyPlaying=false;
if(mCurrentRadius!=mTargetRadius){
revealModified(view, 100);
}
}
});
animIsCurrentlyPlaying=true;
anim.start();
}
}
the method will be excecuted every time you move your finger ever so slightly. I set the duration to 100 ms but that can be shorter if you find its not reactive enough. anytime you move your finger a flag is set so no other animations can be started on top and the transition is always smooth.if you move your finger while an anim is playing then revealModified will be reexecured in the if statement within onAnimationEnd.
let me know if it works and especially if this is what you wanted
I have a circle at the center of the screen inside which there's an ImageView + TextView. I have another two ImageView+TextView, one at the top and another at bottom of the screen.
My requirement is :
I want a copy of the top ImageView+TextView and a copy of the bottom ImageView+TextView to move in animation into the center of the circle, thereby changing the value of the textView inside the circle.
For example:
Say top textView has value 200 and bottom textview has value 300. I want a portion of those values (say 100 or 150) to animate and move into the circle, but the original values 200 and 300 should remain on the same position.
I've tried using TranslateAnimation. However I face issues finding the x and y coordinates of the center circle. It is not exactly going to the center of the circle. Also original view's position is not retained.
TranslateAnimation animation = new
TranslateAnimation(startLayout.getX(),endLayout.getX(),
startLayout.getY(),endLayout.getY);
animation.setDuration(1000);
animation.setFillAfter(false);
startView.startAnimation(animation);
startLayout is the linearlayout in which ImageView and TextView reside.
Please help! Thanks!
I had the same issue and I fixed by using the next code (sorry is in Kotlin, but works the same in Java).Let's say viewFirst wants to reach viewTwo position:
(DON'T USE):
viewFirst.animate()
.translationX(viewSecond.x)
.translationY(viewSecond.y)
.setDuration(1000)
.withEndAction {
//to make sure that it arrives,
//but not needed actually these two lines
viewFirst.x = viewSecond.x
viewFirst.y = viewSecond.y
}
.start()
(USE THIS SOLUTION):
viewFirst.animate()
.x(viewSecond.x)
.y(viewSecond.y)
.setDuration(1000)
.withEndAction {
//to make sure that it arrives,
//but not needed actually these two lines
viewFirst.x = viewSecond.x
viewFirst.y = viewSecond.y
}
.start()
Using the getX() and getY() methods define the position of the view in pixels, but the constructor you use defines Float type values that must be values from 0.0f to 1.0f
TranslateAnimation(float fromXDelta, float toXDelta, float fromYDelta, float toYDelta)
This is another option using the view`s position in pixels:
viewFirst.animate()
.x(viewSecond.getX())
.y(viewSecond.getY())
.setDuration(1000).withEndAction(new Runnable() {
#Override
public void run() {
viewFirst.setX(tv2.getX());
viewFirst.setY(tv2.getY());
}
}).start();
Try this for accurate coordinates
private fun moveView(viewToBeMoved: View, targetView: View) {
val targetX: Float =
targetView.x + targetView.width / 2 - viewToBeMoved.width / 2
val targetY: Float =
targetView.y + targetView.height / 2 - viewToBeMoved.height / 2
viewToBeMoved.animate()
.x(targetX)
.y(targetY)
.setDuration(2000)
.withEndAction {
targetView.visibility = View.GONE
}
.start()
}
I have sliding up panel and in the titlebar of that panel I have ImageView as in the picture. How to rotate to 180* by clockwise that ImageView when user slide panel up and anti-clockwise when slide down.
For sliding that panel I have method:
#Override
public void onPanelSlide(View panel, float slideOffset) {
Log.i(TAG, "onPanelSlide, offset " + slideOffset);
//Animation here
}
Thanks for any help!
If you want to rotate a view without animation:
view.setRotation(float) sets the degrees that the view is
rotated around the pivot point. Increasing values result in clockwise
rotation.
view.setRotationX(float) sets the degrees that the view is
rotated around the horizontal axis through the pivot point.
Increasing values result in clockwise rotation from the viewpoint of
looking down the x axis. When rotating large views, it is recommended to adjust the camera distance accordingly.
view.setRotationY(float) sets the degrees that the view is
rotated around the vertical axis through the pivot point. Increasing
values result in counter-clockwise rotation from the viewpoint of
looking down the y axis. When rotating large views, it is recommended to adjust the camera distance accordingly.
If you want to use animation:
https://github.com/daimajia/AndroidViewAnimations (recommended)
https://github.com/castorflex/FlipImageView
https://github.com/florent37/ViewAnimator
https://github.com/alexvasilkov/GestureViews
https://gist.github.com/simon-heinen/9795036
https://gist.github.com/methodin/5678214
https://www.google.com/ :D
I didn't want to mark this as an answer but I can't leave comments yet, I was able to get Apurva's solution working correctly but I had to use this instead of getApplicationContext(), I had to use this because of where I was setting the animation from, the code in my application where I am attaching the animation looks like this...
ImageView billysArm = (ImageView) findViewById(R.id.imageView6);
Animation rotate = AnimationUtils.loadAnimation(this, R.anim.rotate);
billysArm.startAnimation(rotate);
hope this helps
You should try this out, setting an animation for your e.g. ImageView
#Override
public void onPanelSlide(View panel, float slideOffset) {
Log.i(TAG, "onPanelSlide, offset " + slideOffset);
// Locate view
ImageView iv= (ImageView) findViewById(R.id.iv);
// Create an animation instance
Animation an = new RotateAnimation(0.0f, 180.0f, pivotX, pivotY);
// Set the animation's parameters
an.setDuration(10000); // duration in ms
an.setRepeatCount(0); // -1 = infinite repeated
an.setRepeatMode(Animation.REVERSE); // reverses each repeat
an.setFillAfter(true); // keep rotation after animation
// Aply animation to image view
iv.setAnimation(an);
}
I have a strange problem with the ViewOutlineProvider for a clipped View on Android L.
I try to increase the size of the clip circle while moving the finger over the screen (x coordinates etc.). (Like on the Android L lock screen, left and right icons, which increases when you drag your finger in the specific direction)
The size changing works perfectly, until the clip bounds reaches the left or bottom border of the screen, after this the clip bounds are disappeared and i don't have any clip bounds. (Nothing rounded)
The Rect values, which causes the problem:
Rect(-1, 1033 - 101, 1136) (Rect(left, top - right, bottom))
I initiate and add the view, which i want to clip, in this way:
toClipView = new ViewClipTest(getContext());
toClipView.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
addView(toClipView);
The clip view has a gray background and a red ImageView as child, it extends a FrameLayout
Update code for clip bounds scaling (is called from a onTouchEvent, while ACTION_MOVE):
public void scaleClipbounds(float convertScale) {//-1 to 1
float scale = ...;
currentScale = scale;
...
post(new Runnable() {
#Override
public void run() {
setOutlineProvider(new ClipOutline(createScaledRect(currentScale)));
invalidate();
}
});
}
...
case MotionEvent.ACTION_MOVE:
newX = event.getRawX();
newY = event.getRawY();
float newScale = ...;
scaleClipbounds(newScale);
...
My ViewOutlineProvider:
public class ClipOutline extends ViewOutlineProvider {
private Rect outerLineRect;
public ClipOutline(Rect outerLineRect) {
this.outerLineRect = outerLineRect;
}
#Override
public void getOutline(View view, Outline outline) {
outline.setOval(outerLineRect);
}
}
Xml structure:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.example.ClipViewTestContainer
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</RelativeLayout>
If I initiate the clip bounds with the same rect, which causes the issue, everything works well, until I try to increase the bounds while onTouch again.
What is my mistake here? Or how can I force that the clip bounds can be set out of the screen boundaries? Is there a more fluent way to implement such kind of feature, instead of using an ViewOutlineProvider?
The problem is that your rect you're passing into setOval (-1, 1033 - 101, 1136) has size 102 x 103, which means your outline won't be a circle, and thus won't be eligible for clipping. Only circles, rects, and round rects can be clipped to, see https://developer.android.com/reference/android/graphics/Outline.html#canClip()
Additionally, you can significantly simplify your runnable / outline provider creation by, within scaleClipBounds, updating an existing custom outline provider, and calling View#invalidateOutline().
public void scaleClipbounds(float convertScale) {
...
mOutlineProvider.setRect(rect)
invalidateOutline()
}
I have a button that I want to put on a 45 degree angle. For some reason I can't get this to work.
Can someone please provide the code to accomplish this?
API 11 added a setRotation() method to all views.
You could create an animation and apply it to your button view. For example:
// Locate view
ImageView diskView = (ImageView) findViewById(R.id.imageView3);
// Create an animation instance
Animation an = new RotateAnimation(0.0f, 360.0f, pivotX, pivotY);
// Set the animation's parameters
an.setDuration(10000); // duration in ms
an.setRepeatCount(0); // -1 = infinite repeated
an.setRepeatMode(Animation.REVERSE); // reverses each repeat
an.setFillAfter(true); // keep rotation after animation
// Aply animation to image view
diskView.setAnimation(an);
Extend the TextView class and override the onDraw() method. Make sure the parent view is large enough to handle the rotated button without clipping it.
#Override
protected void onDraw(Canvas canvas) {
canvas.save();
canvas.rotate(45,<appropriate x pivot value>,<appropriate y pivot value>);
super.onDraw(canvas);
canvas.restore();
}
I just used the simple line in my code and it works :
myCusstomView.setRotation(45);
Hope it works for you.
One line in XML
<View
android:rotation="45"
... />
Applying a rotation animation (without duration, thus no animation effect) is a simpler solution than either calling View.setRotation() or override View.onDraw method.
// substitude deltaDegrees for whatever you want
RotateAnimation rotate = new RotateAnimation(0f, deltaDegrees,
Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
// prevents View from restoring to original direction.
rotate.setFillAfter(true);
someButton.startAnimation(rotate);
Rotating view with rotate() will not affect your view's measured size. As result, rotated view be clipped or not fit into the parent layout. This library fixes it though:
https://github.com/rongi/rotate-layout
Joininig #Rudi's and #Pete's answers. I have created an RotateAnimation that keeps buttons functionality also after rotation.
setRotation() method preserves buttons functionality.
Code Sample:
Animation an = new RotateAnimation(0.0f, 180.0f, mainLayout.getWidth()/2, mainLayout.getHeight()/2);
an.setDuration(1000);
an.setRepeatCount(0);
an.setFillAfter(false); // DO NOT keep rotation after animation
an.setFillEnabled(true); // Make smooth ending of Animation
an.setAnimationListener(new AnimationListener() {
#Override
public void onAnimationStart(Animation animation) {}
#Override
public void onAnimationRepeat(Animation animation) {}
#Override
public void onAnimationEnd(Animation animation) {
mainLayout.setRotation(180.0f); // Make instant rotation when Animation is finished
}
});
mainLayout.startAnimation(an);
mainLayout is a (LinearLayout) field
As mentioned before, the easiest way it to use rotation available since API 11:
android:rotation="90" // in XML layout
view.rotation = 90f // programatically
You can also change pivot of rotation, which is by default set to center of the view. This needs to be changed programatically:
// top left
view.pivotX = 0f
view.pivotY = 0f
// bottom right
view.pivotX = width.toFloat()
view.pivotY = height.toFloat()
...
In Activity's onCreate() or Fragment's onCreateView(...) width and height are equal to 0, because the view wasn't measured yet. You can access it simply by using doOnPreDraw extension from Android KTX, i.e.:
view.apply {
doOnPreDraw {
pivotX = width.toFloat()
pivotY = height.toFloat()
}
}
if you wish to make it dynamically with an animation:
view.animate()
.rotation(180)
.start();
THATS IT
#Ichorus's answer is correct for views, but if you want to draw rotated rectangles or text, you can do the following in your onDraw (or onDispatchDraw) callback for your view:
(note that theta is the angle from the x axis of the desired rotation, pivot is the Point that represents the point around which we want the rectangle to rotate, and horizontalRect is the rect's position "before" it was rotated)
canvas.save();
canvas.rotate(theta, pivot.x, pivot.y);
canvas.drawRect(horizontalRect, paint);
canvas.restore();
fun rotateArrow(view: View): Boolean {
return if (view.rotation == 0F) {
view.animate().setDuration(200).rotation(180F)
true
} else {
view.animate().setDuration(200).rotation(0F)
false
}
}
That's simple,
in Java
your_component.setRotation(15);
or
your_component.setRotation(295.18f);
in XML
<Button android:rotation="15" />