1: AccelerateInterpolator加速插值器
1.1 :Android系统资源 ID : @android:anim/accelerate_interpolator 加速插值器
表示:起始速度是零,速度越来越快,加速运动
1.2 :加速插值器源码:
public class AccelerateDecelerateInterpolator extends BaseInterpolator
implements NativeInterpolatorFactory {
public AccelerateDecelerateInterpolator() {
}
@SuppressWarnings({"UnusedDeclaration"})
public AccelerateDecelerateInterpolator(Context context, AttributeSet attrs) {
}
// 主要通过此函数,来得到 x 和 y 轴的 速率关系曲线
public float getInterpolation(float input) {
return (float)(Math.cos((input + 1) * Math.PI) / 2.0f) + 0.5f;
}
/** @hide */
@Override
public long createNativeInterpolator() {
return NativeInterpolatorFactoryHelper.createAccelerateDecelerateInterpolator();
}
}
2: 效果图
?2.1 :效果图实现分解
重点是两部分功能实现:绘制轨迹曲线和实现加速动画
2.1.1 绘制轨迹曲线
直接在 自定义View的 onDraw(Canvas canvas) 函数中,通过 Path这个工具类,结合轨迹点坐标集合,可以绘制出轨迹曲线。
TimeInterpolatorView.java
// 绘制轨迹 Path工具
private Path mDataPath = new Path();
// 轨迹坐标集合
private final List<PointF> mLineDataList = new ArrayList<>();
// 点坐标封装
PointF
@Override
protected void onDraw(Canvas canvas) {
// 绘制轨迹
mDataPath.reset();
float width = mEachItemWidth * GRID_INTERVAL_COUNT;
// 构建 路径,并选出最高和最低的point
for (int i = 0; i < mLineDataList.size(); i++) {
PointF curPoint = mLineDataList.get(i);
if (i == 0) {
mDataPath.moveTo(curPoint.x * width, -curPoint.y * width);
} else {
mDataPath.lineTo(curPoint.x * width, -curPoint.y * width);
}
}
}
2.1.2 :生成轨迹曲线的动画
动画的生成我们分解为:X轴动画,Y轴动画,动画集
animatorSet.play(mAnimator).with(mXAnimator);
X轴动画:线性动画
初始化:mXAnimator = ValueAnimator.ofFloat(0f, 1f);
添加线性插值器 :
mXAnimator.setInterpolator(new LinearInterpolator());
mXAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float x = animation.getAnimatedFraction();
float y = mInterpolator.getInterpolation(x);
curPoint.x = x;
curPoint.y = y;
Log.i("zincTest", "onAnimationUpdate: [" + x + "," + y + "】");
mTimeInterpolatorView.setCurPoint(curPoint);
}
});
Y轴动画
mAnimator = ObjectAnimator.ofFloat(animView, "y", start, end);
float mInterpolator = AccelerateDecelerateInterpolator.getInterpolation();
mAnimator.setInterpolator(mInterpolator);
动画集
animatorSet = new AnimatorSet();
animatorSet.play(mAnimator).with(mXAnimator);
animatorSet.setDuration(duration);
animatorSet.start();
3:源码示例
/**
* @author yuhongwen
* @date 创建时间:2022/04/06
* @description 插值器的坐标显示
*/
public class TimeInterpolatorView extends View {
// 外边距
private static final int PADDING = dpToPx(5f);
// 字体大小
private static final int TEXT_SIZE = dpToPx(8f);
// 点的半径
private static final int CUR_POINT_RADIUS = dpToPx(4.5f);
// X、Y 轴色
private static final int COORDINATION_LINE_COLOR = Color.BLACK;
// 网格线色
private static final int GRID_LINE_COLOR = Color.LTGRAY;
// 数据线色
private static final int DATA_LINE_COLOR = Color.parseColor("#DB001B");
// 当前点的色
private static final int CUR_POINT_COLOR = Color.parseColor("#DC143C");
// 默认的最低点
private static final PointF DEFAULT_MIN_POINT = new PointF(0, 0);
// 默认的最高点
private static final PointF DEFAULT_MAX_POINT = new PointF(0, 1);
// 10个间隔
private static final int GRID_INTERVAL_COUNT = 10;
// 每个间隔的跨幅
private static final float GRID_INTERVAL_LENGTH = 0.1f;
// 速率的数据
private final List<PointF> mLineDataList = new ArrayList<>();
// 坐标的画笔
private Paint mLinePaint;
// 速率的轨迹
private Path mDataPath = new Path();
// 字体画笔
private Paint mTextPaint;
// 点的笔
private Paint mPointPaint;
// 数据的最低点
private PointF mMinPoint = DEFAULT_MIN_POINT;
// 数据的最高点
private PointF mMaxPoint = DEFAULT_MAX_POINT;
// 视图的宽
private float mViewWidth;
// 视图的高
private float mViewHeight;
// 坐标的宽
private float mWidth;
// 坐标中每个下标 的宽度
private float mEachItemWidth;
private int mPositiveCount;
private int mNegativeCount;
// 当前的点
private PointF mCurPoint;
public TimeInterpolatorView(Context context) {
this(context, null, 0);
}
public TimeInterpolatorView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public TimeInterpolatorView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context);
}
private void init(Context context) {
mLinePaint = new Paint();
mLinePaint.setAntiAlias(true);
mLinePaint.setStyle(Paint.Style.STROKE);
mTextPaint = new Paint();
mTextPaint.setAntiAlias(true);
mTextPaint.setTextSize(TEXT_SIZE);
mPointPaint = new Paint();
mPointPaint.setAntiAlias(true);
mPointPaint.setStyle(Paint.Style.FILL);
}
/**
* 设置当前移动的点
*
* @param curPoint
*/
public void setCurPoint(PointF curPoint) {
this.mCurPoint = curPoint;
invalidate();
}
public void setLineData(List<PointF> lineDataList) {
mLineDataList.clear();
mLineDataList.addAll(lineDataList);
mMinPoint = DEFAULT_MIN_POINT;
mMaxPoint = DEFAULT_MAX_POINT;
// 构建 路径,并选出最高和最低的point
for (int i = 0; i < mLineDataList.size(); i++) {
PointF curPoint = mLineDataList.get(i);
// 选最低点
if (curPoint.y < mMinPoint.y) {
mMinPoint = curPoint;
}
// 选最高点
if (curPoint.y > mMaxPoint.y) {
mMaxPoint = curPoint;
}
}
calculateEachItemWidth();
invalidate();
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mViewWidth = w;
mViewHeight = h;
// 需要减去 padding 的宽度 和 字体的大小
mWidth = Math.min(w, h) - 2 * PADDING - TEXT_SIZE;
calculateEachItemWidth();
}
/**
* 计算每个格子的大小
*/
private void calculateEachItemWidth() {
// 获取 y正半轴 的分割个数
mPositiveCount = (int) Math.abs(Math.ceil(mMaxPoint.y / GRID_INTERVAL_LENGTH));
// 获取 y负半轴 的分割个数
mNegativeCount = (int) Math.abs(Math.floor(mMinPoint.y / GRID_INTERVAL_LENGTH));
// 计算需要分割的数量,最少十个
int intervalCount = mPositiveCount + mNegativeCount;
intervalCount = Math.max(intervalCount, GRID_INTERVAL_COUNT);
mEachItemWidth = mWidth / intervalCount;
}
@Override
protected void onDraw(Canvas canvas) {
// 构建轨迹
buildDataPath();
canvas.save();
// 移至原点
moveToTheOrigin(canvas);
// 画坐标
drawCoordination(canvas);
// 画网格
drawGrid(canvas);
// 画数据线
drawDataLine(canvas);
// 画下标
drawText(canvas);
// 画当前的点
drawPoint(canvas);
canvas.restore();
}
/**
* 画点
*
* @param canvas
*/
private void drawPoint(Canvas canvas) {
if (mCurPoint == null) {
return;
}
mPointPaint.setColor(CUR_POINT_COLOR);
canvas.drawCircle(mCurPoint.x * mEachItemWidth * GRID_INTERVAL_COUNT,
-mCurPoint.y * mEachItemWidth * GRID_INTERVAL_COUNT,
CUR_POINT_RADIUS,
mPointPaint);
}
/**
* 画下标
*
* @param canvas
*/
private void drawText(Canvas canvas) {
canvas.drawText("0", -PADDING,
0,
mTextPaint);
mTextPaint.setTextAlign(Paint.Align.RIGHT);
for (int i = 1; i <= mPositiveCount; ++i) {
if (i <= 10) {
mTextPaint.setColor(COORDINATION_LINE_COLOR);
} else {
mTextPaint.setColor(DATA_LINE_COLOR);
}
canvas.drawText(getNumString(i * 0.1f), -PADDING / 2,
-i * mEachItemWidth,
mTextPaint);
}
mTextPaint.setColor(DATA_LINE_COLOR);
for (int i = 1; i <= mNegativeCount; ++i) {
canvas.drawText(getNumString(i * -0.1f), -PADDING / 2,
i * mEachItemWidth,
mTextPaint);
}
mTextPaint.setTextAlign(Paint.Align.CENTER);
mTextPaint.setColor(COORDINATION_LINE_COLOR);
for (int i = 1; i <= GRID_INTERVAL_COUNT; ++i) {
canvas.drawText(getNumString(i * 0.1f), i * mEachItemWidth,
PADDING / 2 + TEXT_SIZE,
mTextPaint);
}
}
private String getNumString(float num) {
return String.format("%.1f", num);
}
/**
* 画网格线
*
* @param canvas
*/
private void drawGrid(Canvas canvas) {
mLinePaint.setStrokeWidth(dpToPx(0.5f));
mLinePaint.setColor(GRID_LINE_COLOR);
// 画y正轴横线
for (int i = 1; i <= mPositiveCount; ++i) {
canvas.drawLine(0,
-i * mEachItemWidth,
GRID_INTERVAL_COUNT * mEachItemWidth,
-i * mEachItemWidth,
mLinePaint);
}
// 画y负轴横线
for (int i = 1; i <= mNegativeCount; ++i) {
canvas.drawLine(0,
i * mEachItemWidth,
GRID_INTERVAL_COUNT * mEachItemWidth,
i * mEachItemWidth,
mLinePaint);
}
// 画x正轴竖线
for (int i = 1; i <= GRID_INTERVAL_COUNT; ++i) {
canvas.drawLine(i * mEachItemWidth,
-mPositiveCount * mEachItemWidth,
i * mEachItemWidth,
mNegativeCount * mEachItemWidth,
mLinePaint);
}
}
/**
* 画数据线
*
* @param canvas
*/
private void drawDataLine(Canvas canvas) {
mLinePaint.setStrokeWidth(dpToPx(1f));
mLinePaint.setColor(DATA_LINE_COLOR);
canvas.drawPath(mDataPath, mLinePaint);
}
/**
* 画 x、y 轴
*
* @param canvas
*/
private void drawCoordination(Canvas canvas) {
mLinePaint.setStrokeWidth(dpToPx(1f));
mLinePaint.setColor(COORDINATION_LINE_COLOR);
// 画 y 轴
canvas.drawLine(0,
-mPositiveCount * mEachItemWidth,
0,
mNegativeCount * mEachItemWidth,
mLinePaint);
// 画 x 轴
canvas.drawLine(0,
0,
GRID_INTERVAL_COUNT * mEachItemWidth,
0,
mLinePaint);
}
/**
* 构建数据路径
*/
private void buildDataPath() {
mDataPath.reset();
float width = mEachItemWidth * GRID_INTERVAL_COUNT;
// 构建 路径,并选出最高和最低的point
for (int i = 0; i < mLineDataList.size(); i++) {
PointF curPoint = mLineDataList.get(i);
if (i == 0) {
mDataPath.moveTo(curPoint.x * width, -curPoint.y * width);
} else {
mDataPath.lineTo(curPoint.x * width, -curPoint.y * width);
}
}
}
/**
* 将画布移至 原点
*/
private void moveToTheOrigin(Canvas canvas) {
float verHeight = mEachItemWidth * mPositiveCount;
// 计算 横向移动距离
float horPadding = mViewWidth - mEachItemWidth * GRID_INTERVAL_COUNT - 2 * PADDING;
float verPadding = mViewHeight - mEachItemWidth * (mPositiveCount + mNegativeCount) - 2 * PADDING - TEXT_SIZE / 2;
canvas.translate(horPadding / 2 + PADDING, verPadding / 2 + verHeight + PADDING);
}
/**
* 转换 dp 至 px
*
* @param dpValue dp值
* @return px值
*/
protected static int dpToPx(float dpValue) {
DisplayMetrics metrics = Resources.getSystem().getDisplayMetrics();
return (int) (dpValue * metrics.density + 0.5f);
}
/**
* 获取视图的宽
*
* @return 视图宽 - 左右的内边距
*/
private float getViewEnableWidth() {
return mViewWidth - PADDING * 2;
}
/**
* 获取视图的高
*
* @return 视图高 - 上下的内边距
*/
private float getViewEnableHeight() {
return mViewHeight - PADDING * 2;
}
public class TimeInterpolatorActivity extends AppCompatActivity implements TimeInterpolatorAdapter.ClickListener {
// 取1000帧
private static final int FRAME = 1000;
private TimeInterpolatorView mTimeInterpolatorView;
private final List<PointF> dataList = new ArrayList<>();
private final List<TimeInterpolatorBean> interpolatorList = new ArrayList<>();
private TimeInterpolator mInterpolator;
private TextView tvRun;
private View animView;
private RecyclerView recycleView;
private TextView tvDuration;
private TextView tvStateInfo;
private TimeInterpolatorAdapter mAdapter;
private ObjectAnimator mAnimator;
private ValueAnimator mXAnimator;
private AnimatorSet animatorSet;
private boolean isRunning;
private PointF curPoint;
private View getStartView() {
return (View) findViewById(R.id.start_view);
}
private View getAnimView() {
return (View) findViewById(R.id.anim_view);
}
private View getEndView() {
return (View) findViewById(R.id.end_view);
}
private EditText getEtDuration() {
return (EditText) findViewById(R.id.et_duration);
}
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_time_interpolator);
mTimeInterpolatorView = findViewById(R.id.time_interpolator_view);
tvRun = findViewById(R.id.tv_run);
animView = findViewById(R.id.anim_view);
recycleView = findViewById(R.id.recycle_view);
tvStateInfo = findViewById(R.id.tv_state_info);
isRunning = false;
curPoint = new PointF(0, 0);
buildInterpolatorList();
createData();
final float start = dpToPx(this, 35);
final float end = getScreenHeight(this) - dpToPx(this, 35 + 50) - getStatusHeight(this);
mAnimator = ObjectAnimator.ofFloat(animView, "y", start, end);
mXAnimator = ValueAnimator.ofFloat(0f, 1f);
animatorSet = new AnimatorSet();
animatorSet.play(mAnimator).with(mXAnimator);
mAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
updateState(false);
}
});
mXAnimator.setInterpolator(new LinearInterpolator());
mXAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float x = animation.getAnimatedFraction();
float y = mInterpolator.getInterpolation(x);
curPoint.x = x;
curPoint.y = y;
Log.i("zincTest", "onAnimationUpdate: [" + x + "," + y + "】");
mTimeInterpolatorView.setCurPoint(curPoint);
}
});
mTimeInterpolatorView.setCurPoint(curPoint);
mTimeInterpolatorView.setLineData(dataList);
// 点击开始----动画运作
tvRun.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (isRunning) {
Toast.makeText(TimeInterpolatorActivity.this, "动画正在进行中,请稍等", Toast.LENGTH_SHORT).show();
return;
}
updateState(true);
String durationString = getEtDuration().getText().toString();
long duration = TextUtils.isEmpty(durationString) ? 2000L : Long.parseLong(durationString);
animatorSet.setDuration(duration);
mAnimator.setInterpolator(mInterpolator);
animatorSet.start();
}
});
mAdapter = new TimeInterpolatorAdapter(this, interpolatorList);
mAdapter.setListener(this);
recycleView.setLayoutManager(new LinearLayoutManager(this));
recycleView.setAdapter(mAdapter);
}
private void createData() {
dataList.clear();
for (float x = 0; x <= 1; x += 1.0f / FRAME) {
float y = mInterpolator.getInterpolation(x);
PointF pointF = new PointF(x, y);
dataList.add(pointF);
}
}
/**
* 初始化插值器,需要的可以在这里添加自己的插值器
*/
private void buildInterpolatorList() {
interpolatorList.clear();
interpolatorList.add(new TimeInterpolatorBean(true, "SpringInterpolator", new SpringInterpolator()));
interpolatorList.add(new TimeInterpolatorBean(false, "AccelerateDecelerateInterpolator", new AccelerateDecelerateInterpolator()));
interpolatorList.add(new TimeInterpolatorBean(false, "AccelerateInterpolator", new AccelerateInterpolator()));
interpolatorList.add(new TimeInterpolatorBean(false, "AnticipateInterpolator", new AnticipateInterpolator()));
interpolatorList.add(new TimeInterpolatorBean(false, "AnticipateOvershootInterpolator", new AnticipateOvershootInterpolator()));
interpolatorList.add(new TimeInterpolatorBean(false, "BounceInterpolator", new BounceInterpolator()));
interpolatorList.add(new TimeInterpolatorBean(false, "CycleInterpolator(1)", new CycleInterpolator(1)));
interpolatorList.add(new TimeInterpolatorBean(false, "DecelerateInterpolator", new DecelerateInterpolator()));
interpolatorList.add(new TimeInterpolatorBean(false, "LinearInterpolator", new LinearInterpolator()));
interpolatorList.add(new TimeInterpolatorBean(false, "OvershootInterpolator", new OvershootInterpolator()));
mInterpolator = interpolatorList.get(0).getTimeInterpolator();
}
public static float getScreenHeight(Context context) {
DisplayMetrics metrics = new DisplayMetrics();
((WindowManager) context.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay().getMetrics(metrics);
return metrics.heightPixels;
}
public static float dpToPx(Context context, float dipValue) {
float density = context.getResources().getDisplayMetrics().density;
return dipValue * density + 0.5f;
}
public static float getStatusHeight(Context context) {
int result = 0;
int resourceId = context.getResources().getIdentifier("status_bar_height", "dimen", "android");
if (resourceId > 0) {
result = context.getResources().getDimensionPixelSize(resourceId);
}
return result;
}
@Override
public void onTimeInterpolatorClick(int position) {
for (TimeInterpolatorBean bean : interpolatorList) {
bean.setSelect(false);
}
interpolatorList.get(position).setSelect(true);
mInterpolator = interpolatorList.get(position).getTimeInterpolator();
mAdapter.notifyDataSetChanged();
createData();
mTimeInterpolatorView.setLineData(dataList);
curPoint.x = 0;
curPoint.y = 0;
mTimeInterpolatorView.setCurPoint(curPoint);
}
private void updateState(boolean isRunning) {
this.isRunning = isRunning;
tvStateInfo.setText(isRunning ? "running" : "ready");
tvStateInfo.setTextColor(isRunning ?
Color.parseColor("#32CD32") :
Color.parseColor("#1E90FF"));
}
}
|