前言
属性动画(ValueAnimator)是 Android3.0 版本推出的动画框架,其功能和拓展性都很强。它不仅能实现所有 Tween 动画的功能,还有很强的拓展性,根本原因是属性动画从本质上已经完全摆脱了控件,虽然我们大多数情况下使用属性动画都是给控件做动画,但是属性动画的底层只是一个数值发生器,和控件没有半毛钱关系。
原理
在一定时间间隔内,通过不断对值进行改变、不断将该值赋给对象的属性(任意对象的任意属性),从而实现该对象在该属性上的动画效果。
ValueAnimator 类
定义:属性动画机制中最核心的一个类。
ValueAnimator类中有3个重要方法:
- ofInt(int values)
- ofFloat(float values)
- ofObject(int values)
1. 效果图
2. 代码
public class MainActivity extends AppCompatActivity {
private Button mButton;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mButton = findViewById(R.id.button);
ValueAnimator valueAnimator = ValueAnimator.ofInt(mButton.getLayoutParams().width, 500);
valueAnimator.setDuration(2000);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animator) {
int currentValue = (Integer) animator.getAnimatedValue();
mButton.getLayoutParams().width = currentValue;
mButton.requestLayout();
}
});
valueAnimator.start();
}
}
ObjectAnimator 类
1. 效果图
2. 代码
public class MainActivity extends AppCompatActivity {
private Button mButton;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mButton = findViewById(R.id.button);
ObjectAnimator animator = ObjectAnimator.ofFloat(mButton, "rotation", 0f, 360f);
animator.setDuration(5000);
animator.start();
}
}
手势检测(GestureDetector)
实例:轨迹球
- 效果图
在开发 Android 手机应用过程中,可能需要对一些手势作出响应,如:单击、双击、长按、滑动、缩放等。这些都是很常用的手势。
2. 布局文件
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<com.scarf.demo007.widget.FailingBall
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</LinearLayout>
3.代码
/**
* Created on 2019/2/12 15:09
* 轨迹球
* @author Scarf Gong
*/
public class FailingBall extends View {
private int mWidth; // 宽度
private int mHeight; // 高度
private float mStartX = 0; // 小方块开始位置X
private float mStartY = 0; // 小方块开始位置Y
private float mEdgeLength = 200; // 边长
private RectF mRect = new RectF(mStartX, mStartY, mStartX + mEdgeLength, mStartY + mEdgeLength);
private float mFixedX = 0; // 修正距离X
private float mFixedY = 0; // 修正距离Y
private Paint mPaint;
private GestureDetector mGestureDetector;
private boolean mCanFail = false; // 是否可以拖动
private float mSpeedX = 0;
private float mSpeedY = 0;
private Boolean mXFixed = false;
private Boolean mYFixed = false;
private Bitmap mBitmap;
public FailingBall(Context context) {
this(context,null);
}
public FailingBall(Context context, @Nullable AttributeSet attrs) {
this(context, attrs,0);
}
public FailingBall(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mGestureDetector = new GestureDetector(context, mSimpleOnGestureListener);
mPaint = new Paint();
mPaint.setColor(Color.BLACK);
mPaint.setAntiAlias(true);
mBitmap = BitmapFactory.decodeResource(context.getResources(), R.drawable.ball);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mWidth = w;
mHeight = h;
mStartX = (w - mEdgeLength) / 2;
mStartY = (h - mEdgeLength) / 2;
refreshRectByCurrentPoint();
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawOval(mRect, mPaint);
canvas.drawBitmap(mBitmap, new Rect(0, 0, mBitmap.getWidth(), mBitmap.getHeight()),
mRect, mPaint);
}
// 每 100 ms 更新一次
private Handler mHandler = new Handler();
private Runnable mRunnable = new Runnable() {
@Override
public void run() {
mStartX = mStartX + mSpeedX / 30;
mStartY = mStartY + mSpeedY / 30;
//mSpeedX = mSpeedX > 0 ? mSpeedX - 10 : mSpeedX + 10;
//mSpeedY = mSpeedY > 0 ? mSpeedY - 10 : mSpeedY + 10;
mSpeedX *= 0.97;
mSpeedY *= 0.97;
if (Math.abs(mSpeedX) < 10) {
mSpeedX = 0;
}
if (Math.abs(mSpeedY) < 10) {
mSpeedY = 0;
}
if (refreshRectByCurrentPoint()) {
// 转向
if (mXFixed) {
mSpeedX = -mSpeedX;
}
if (mYFixed) {
mSpeedY = -mSpeedY;
}
}
invalidate();
if (mSpeedX == 0 && mSpeedY == 0) {
mHandler.removeCallbacks(this);
return;
}
mHandler.postDelayed(this, 33);
}
};
private GestureDetector.SimpleOnGestureListener mSimpleOnGestureListener = new
GestureDetector.SimpleOnGestureListener() {
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float
velocityY) {
Log.e("Failing", velocityX + " : " + velocityY);
if (!mCanFail) return false;
mSpeedX = velocityX;
mSpeedY = velocityY;
mHandler.removeCallbacks(mRunnable);
mHandler.postDelayed(mRunnable, 0);
return super.onFling(e1, e2, velocityX, velocityY);
}
};
@Override
public boolean onTouchEvent(MotionEvent event) {
mGestureDetector.onTouchEvent(event);
switch (event.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
if (contains(event.getX(), event.getY())) {
mCanFail = true;
mFixedX = event.getX() - mStartX;
mFixedY = event.getY() - mStartY;
mSpeedX = 0;
mSpeedY = 0;
} else {
mCanFail = false;
}
break;
case MotionEvent.ACTION_MOVE:
if (!mCanFail) {
break;
}
mStartX = event.getX() - mFixedX;
mStartY = event.getY() - mFixedY;
if (refreshRectByCurrentPoint()) {
mFixedX = event.getX() - mStartX;
mFixedY = event.getY() - mStartY;
}
invalidate();
break;
}
return true;
}
private Boolean contains(float x, float y) {
float radius = mEdgeLength / 2;
float centerX = mRect.left + radius;
float centerY = mRect.top + radius;
return Math.sqrt(Math.pow(x - centerX, 2) + Math.pow(y - centerY, 2)) <= radius;
}
/**
* 刷新方块位置
*
* @return true 表示修正过位置, false 表示没有修正过位置
*/
private Boolean refreshRectByCurrentPoint() {
Boolean fixed = false;
mXFixed = false;
mYFixed = false;
// 修正坐标
if (mStartX < 0) {
mStartX = 0;
fixed = true;
mXFixed = true;
}
if (mStartY < 0) {
mStartY = 0;
fixed = true;
mYFixed = true;
}
if (mStartX + mEdgeLength > mWidth) {
mStartX = mWidth - mEdgeLength;
fixed = true;
mXFixed = true;
}
if (mStartY + mEdgeLength > mHeight) {
mStartY = mHeight - mEdgeLength;
fixed = true;
mYFixed = true;
}
mRect.left = mStartX;
mRect.top = mStartY;
mRect.right = mStartX + mEdgeLength;
mRect.bottom = mStartY + mEdgeLength;
return fixed;
}
}