Android app has a custom view(screen overlay) with animation
animation rendering is good on devices below android 7.1.1 but the animation is not rending properly on android oreo 8.0 and 8.1
Screen record of 7.1.1
Screen record of 8.0
My Circle Layout code
public class CircleLayout extends ViewGroup {
private final Context context;
public enum FirstChildPosition {
EAST(0), SOUTH(125), WEST(180), NORTH(270);
private int angle;
FirstChildPosition(int angle) {
this.angle = angle;
}
public int getAngle() {
return angle;
}
}
// public MediaPlayer mp;
// Event listeners
private OnItemClickListener onItemClickListener = null;
private OnItemSelectedListener onItemSelectedListener = null;
private OnCenterClickListener onCenterClickListener = null;
private OnRotationFinishedListener onRotationFinishedListener = null;
// Sizes of the ViewGroup
private int circleWidth, circleHeight;
private float radius = -1;
// Child sizes
private int maxChildWidth = 0;
private int maxChildHeight = 0;
// Touch detection
private GestureDetector gestureDetector;
// Detecting inverse rotations
private boolean[] quadrantTouched;
// Settings of the ViewGroup
private int speed = 25;
private float angle = 90;
private FirstChildPosition firstChildPosition = FirstChildPosition.SOUTH;
private boolean isRotating = true;
// Touch helpers
private double touchStartAngle;
private boolean didMove = false;
// Tapped and selected child
private View selectedView = null;
// Rotation animator
private ObjectAnimator animator;
public CircleLayout(Context context) {
this(context, null);
}
public CircleLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public CircleLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(attrs);
this.context=context;
// mp= MediaPlayer.create(context, R.raw.rotation_sound);
}
/**
* Initializes the ViewGroup and modifies it's default behavior by the
* passed attributes
*
* @param attrs the attributes used to modify default settings
*/
protected void init(AttributeSet attrs) {
gestureDetector = new GestureDetector(getContext(),
new MyGestureListener());
quadrantTouched = new boolean[]{false, false, false, false, false};
if (attrs != null) {
TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.CircleLayout);
speed = a.getInt(R.styleable.CircleLayout_speed, speed);
radius = a.getDimension(R.styleable.CircleLayout_radius, radius);
isRotating = a.getBoolean(R.styleable.CircleLayout_isRotating, isRotating);
// The angle where the first menu item will be drawn
angle = a.getInt(R.styleable.CircleLayout_firstChildPosition, (int) angle);
for (FirstChildPosition pos : FirstChildPosition.values()) {
if (pos.getAngle() == angle) {
firstChildPosition = pos;
break;
}
}
a.recycle();
// Needed for the ViewGroup to be drawn
setWillNotDraw(false);
}
}
public float getAngle() {
return angle;
}
public void setAngle(float angle) {
this.angle = angle % 360;
setChildAngles();
}
public int getSpeed() {
return speed;
}
public void setSpeed(int speed) {
if (speed <= 0) {
throw new InvalidParameterException("Speed must be a positive integer number");
}
this.speed = speed;
}
public float getRadius() {
return radius;
}
public void setRadius(float radius) {
if(radius < 0){
throw new InvalidParameterException("Radius cannot be negative");
}
this.radius = radius;
setChildAngles();
}
public boolean isRotating() {
return isRotating;
}
public void setRotating(boolean isRotating) {
this.isRotating = isRotating;
}
public FirstChildPosition getFirstChildPosition() {
return firstChildPosition;
}
public void setFirstChildPosition(FirstChildPosition firstChildPosition) {
this.firstChildPosition = firstChildPosition;
if (selectedView != null) {
if (isRotating) {
rotateViewToCenter(selectedView);
} else {
setAngle(firstChildPosition.getAngle());
}
}
}
/**
* Returns the currently selected menu
*
* @return the view which is currently the closest to the first item
* position
*/
public View getSelectedItem() {
if (selectedView == null) {
selectedView = getChildAt(0);
}
return selectedView;
}
@Override
public void removeView(View view) {
super.removeView(view);
updateAngle();
}
@Override
public void removeViewAt(int index) {
super.removeViewAt(index);
updateAngle();
}
@Override
public void removeViews(int start, int count) {
super.removeViews(start, count);
updateAngle();
}
@Override
public void addView(View child, int index, LayoutParams params) {
super.addView(child, index, params);
updateAngle();
}
private void updateAngle() {
// Update the position of the views, so we know which is the selected
setChildAngles();
rotateViewToCenter(selectedView);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// Measure child views first
maxChildWidth = 0;
maxChildHeight = 0;
int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(
MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.AT_MOST);
int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(
MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.AT_MOST);
final int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
final View child = getChildAt(i);
if (child.getVisibility() == GONE) {
continue;
}
measureChild(child, childWidthMeasureSpec, childHeightMeasureSpec);
maxChildWidth = Math.max(maxChildWidth, child.getMeasuredWidth());
maxChildHeight = Math.max(maxChildHeight, child.getMeasuredHeight());
}
// Then decide what size we want to be
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int width;
int height;
//Measure Width
if (widthMode == MeasureSpec.EXACTLY) {
//Must be this size
width = widthSize;
} else if (widthMode == MeasureSpec.AT_MOST) {
//Can't be bigger than...
width = Math.min(widthSize, heightSize);
} else {
//Be whatever you want
width = maxChildWidth * 3;
}
//Measure Height
if (heightMode == MeasureSpec.EXACTLY) {
//Must be this size
height = heightSize;
} else if (heightMode == MeasureSpec.AT_MOST) {
//Can't be bigger than...
height = Math.min(heightSize, widthSize);
} else {
//Be whatever you want
height = maxChildHeight * 3;
}
setMeasuredDimension(resolveSize(width, widthMeasureSpec),
resolveSize(height, heightMeasureSpec));
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int layoutWidth = r - l;
int layoutHeight = b - t;
if(radius < 0) {
radius = (layoutWidth <= layoutHeight) ? layoutWidth / 3
: layoutHeight / 3;
}
circleHeight = getHeight();
circleWidth = getWidth();
setChildAngles();
}
/**
* Rotates the given view to the firstChildPosition
*
* @param view the view to be rotated
*/
public void rotateViewToCenter(View view) {
if (isRotating) {
float viewAngle = view.getTag() != null ? (Float) view.getTag() : 0;
float destAngle = firstChildPosition.getAngle() - viewAngle;
if (destAngle < 0) {
destAngle += 360;
}
if (destAngle > 180) {
destAngle = -1 * (360 - destAngle);
}
animateTo(angle + destAngle, 7500L / speed);
}
}
private void rotateButtons(float degrees) {
angle += degrees;
setChildAngles();
}
private void setChildAngles() {
int left, top, childWidth, childHeight, childCount = getChildCount();
float angleDelay = 360.0f / childCount;
float halfAngle = angleDelay / 2;
float localAngle = angle;
for (int i = 0; i < childCount; i++) {
final View child = getChildAt(i);
if (child.getVisibility() == GONE) {
continue;
}
if (localAngle > 360) {
localAngle -= 360;
} else if (localAngle < 0) {
localAngle += 360;
}
childWidth = child.getMeasuredWidth();
childHeight = child.getMeasuredHeight();
left = Math
.round((float) (((circleWidth / 2.0) - childWidth / 2.0) + radius
* Math.cos(Math.toRadians(localAngle))));
top = Math
.round((float) (((circleHeight / 2.0) - childHeight / 2.0) + radius
* Math.sin(Math.toRadians(localAngle))));
child.setTag(localAngle);
float distance = Math.abs(localAngle - firstChildPosition.getAngle());
boolean isFirstItem = distance <= halfAngle || distance >= (360 - halfAngle);
if (isFirstItem && selectedView != child) {
selectedView = child;
if (onItemSelectedListener != null && isRotating) {
onItemSelectedListener.onItemSelected(child);
}
}
child.layout(left, top, left + childWidth, top + childHeight);
localAngle += angleDelay;
}
}
private void animateTo(float endDegree, long duration) {
if (animator != null && animator.isRunning() || Math.abs(angle - endDegree) < 1) {
return;
}
animator = ObjectAnimator.ofFloat(CircleLayout.this, "angle", angle, endDegree);
animator.setDuration(duration);
animator.setInterpolator(new DecelerateInterpolator());
animator.addListener(new Animator.AnimatorListener() {
private boolean wasCanceled = false;
@Override
public void onAnimationStart(Animator animation) {
}
@Override
public void onAnimationRepeat(Animator animation) {
}
@Override
public void onAnimationEnd(Animator animation) {
if (wasCanceled) {
return;
}
if (onRotationFinishedListener != null) {
View view = getSelectedItem();
onRotationFinishedListener.onRotationFinished(view);
}
}
@Override
public void onAnimationCancel(Animator animation) {
wasCanceled = true;
}
});
animator.start();
}
private void stopAnimation() {
if (animator != null && animator.isRunning()) {
animator.cancel();
animator = null;
}
/* if (!isRotating) {
if (mp.isPlaying()) {
mp.stop();
mp.release();
}
}*/
}
/**
* @return The angle of the unit circle with the image views center
*/
private double getPositionAngle(double xTouch, double yTouch) {
double x = xTouch - (circleWidth / 2d);
double y = circleHeight - yTouch - (circleHeight / 2d);
switch (getPositionQuadrant(x, y)) {
case 1:
return Math.asin(y / Math.hypot(x, y)) * 180 / Math.PI;
case 2:
case 3:
return 180 - (Math.asin(y / Math.hypot(x, y)) * 180 / Math.PI);
case 4:
return 360 + Math.asin(y / Math.hypot(x, y)) * 180 / Math.PI;
default:
// ignore, does not happen
return 0;
}
}
/**
* @return The quadrant of the position
*/
private static int getPositionQuadrant(double x, double y) {
if (x >= 0) {
return y >= 0 ? 1 : 4;
} else {
return y >= 0 ? 2 : 3;
}
}
@Override
public boolean onTouchEvent(MotionEvent event) {
if (isEnabled()) {
gestureDetector.onTouchEvent(event);
if (isRotating) {
/* if (!mp.isPlaying())
{
mp.start();
} */
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
// reset the touched quadrants
for (int i = 0; i < quadrantTouched.length; i++) {
quadrantTouched[i] = false;
}
stopAnimation();
touchStartAngle = getPositionAngle(event.getX(),
event.getY());
didMove = false;
break;
case MotionEvent.ACTION_MOVE:
double currentAngle = getPositionAngle(event.getX(),
event.getY());
rotateButtons((float) (touchStartAngle - currentAngle));
touchStartAngle = currentAngle;
didMove = true;
break;
case MotionEvent.ACTION_UP:
if (didMove) {
rotateViewToCenter(selectedView);
}
break;
default:
break;
}
}
// set the touched quadrant to true
quadrantTouched[getPositionQuadrant(event.getX()
- (circleWidth / 2), circleHeight - event.getY()
- (circleHeight / 2))] = true;
return true;
}
return false;
}
private class MyGestureListener extends SimpleOnGestureListener {
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
if (!isRotating) {
/* if (mp.isPlaying())
{
mp.stop();
mp.release();
} */
return false;
}
// get the quadrant of the start and the end of the fling
int q1 = getPositionQuadrant(e1.getX() - (circleWidth / 2),
circleHeight - e1.getY() - (circleHeight / 2));
int q2 = getPositionQuadrant(e2.getX() - (circleWidth / 2),
circleHeight - e2.getY() - (circleHeight / 2));
if ((q1 == 2 && q2 == 2 && Math.abs(velocityX) < Math
.abs(velocityY))
|| (q1 == 3 && q2 == 3)
|| (q1 == 1 && q2 == 3)
|| (q1 == 4 && q2 == 4 && Math.abs(velocityX) > Math
.abs(velocityY))
|| ((q1 == 2 && q2 == 3) || (q1 == 3 && q2 == 2))
|| ((q1 == 3 && q2 == 4) || (q1 == 4 && q2 == 3))
|| (q1 == 2 && q2 == 4 && quadrantTouched[3])
|| (q1 == 4 && q2 == 2 && quadrantTouched[3])) {
// the inverted rotations
animateTo(
getCenteredAngle(angle - (velocityX + velocityY) / 25),
25000L / speed);
} else {
// the normal rotation
animateTo(
getCenteredAngle(angle + (velocityX + velocityY) / 25),
25000L / speed);
}
return true;
}
private float getCenteredAngle(float angle) {
if (getChildCount() == 0) {
// Prevent divide by zero
return angle;
}
float angleDelay = 360 / getChildCount();
float localAngle = angle % 360;
if (localAngle < 0) {
localAngle = 360 + localAngle;
}
for (float i = firstChildPosition.getAngle(); i < firstChildPosition.getAngle() + 360; i += angleDelay) {
float locI = i % 360;
float diff = localAngle - locI;
if (Math.abs(diff) < angleDelay) {
angle -= diff;
break;
}
}
return angle;
}
@Override
public boolean onSingleTapUp(MotionEvent e) {
View tappedView = null;
int tappedViewsPosition = pointToChildPosition(e.getX(), e.getY());
if (tappedViewsPosition >= 0) {
tappedView = getChildAt(tappedViewsPosition);
tappedView.setPressed(true);
} else {
// Determine if it was a center click
float centerX = circleWidth / 2F;
float centerY = circleHeight / 2F;
if (onCenterClickListener != null
&& e.getX() < centerX + radius - (maxChildWidth / 2)
&& e.getX() > centerX - radius + (maxChildWidth / 2)
&& e.getY() < centerY + radius - (maxChildHeight / 2)
&& e.getY() > centerY - radius + (maxChildHeight / 2)) {
onCenterClickListener.onCenterClick();
return true;
}
}
if (tappedView != null) {
// if (selectedView == tappedView) {
if (onItemClickListener != null) {
onItemClickListener.onItemClick(tappedView);
}
// } else {
// rotateViewToCenter(tappedView);
// if (!isRotating) {
// if (onItemSelectedListener != null) {
// onItemSelectedListener.onItemSelected(tappedView);
// }
//
// if (onItemClickListener != null) {
// onItemClickListener.onItemClick(tappedView);
// }
// }
// }
return true;
}
return super.onSingleTapUp(e);
}
private int pointToChildPosition(float x, float y) {
for (int i = 0; i < getChildCount(); i++) {
View view = getChildAt(i);
if (view.getLeft() < x && view.getRight() > x
& view.getTop() < y && view.getBottom() > y) {
return i;
}
}
return -1;
}
}
public interface OnItemClickListener {
void onItemClick(View view);
}
public void setOnItemClickListener(OnItemClickListener onItemClickListener) {
this.onItemClickListener = onItemClickListener;
}
public interface OnItemSelectedListener {
void onItemSelected(View view);
}
public void setOnItemSelectedListener(
OnItemSelectedListener onItemSelectedListener) {
this.onItemSelectedListener = onItemSelectedListener;
}
public interface OnCenterClickListener {
void onCenterClick();
}
public void setOnCenterClickListener(
OnCenterClickListener onCenterClickListener) {
this.onCenterClickListener = onCenterClickListener;
}
public interface OnRotationFinishedListener {
void onRotationFinished(View view);
}
public void setOnRotationFinishedListener(
OnRotationFinishedListener onRotationFinishedListener) {
this.onRotationFinishedListener = onRotationFinishedListener;
}
}
Add this in application tag of manifest
<application android:hardwareAccelerated="true" ...>
HardwareAcceleration fixed the issue now animation is really smooth
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.