网站首页 > 技术文章 正文
前言
android开发中动画有多么重要,相信大家都清楚。它可以让一个枯燥乏味的静态界面变成一个充满动力的动画世界,提高用户体验。反正现在都是用户体验至上。android也是前端。
废话不多少。直接上效果图
主要就是中间那部分的动画效果。
理解Android中动画实现的本质
在理解Android中动画实现的本质之前,首先要理解动画实现的原理,估计这个大家都清楚。
如果要在Android中实现动画展示,那么就必须要有一个“动画驱动”每隔1/24秒去调用View的draw()方法,同时改变每一帧中View需要变化的元素,让这个View不断的绘制,这样一来,所有变化就是组合成一个流畅的动画。
上面就是“Android中动画实现的本质”,其关键就是要有一个“动画驱动”。回想下我们平时最常用的动画类Animation或者Animator,其实它们内部实现也是一个“动画驱动”,驱动View不断绘制。所以,我们完全可以不用Animation或者Animator去做动画,只要有一个“驱动”即可,例如Scroller是个不错的选择,甚至我们可以写一个我们自己实现的“动画驱动”。
常用的“动画驱动”
View本身
view本身的onDraw()马上会触发下一次绘制。
class MyView extends View {
public void onDraw(Canvas canvas) {
super.onDraw(canvas);
invalidate();
}
}
2. View动画,属性动画(Animation/Animator)
上面的部分就是使用属性动画.
3. Scroller
这个在刚开始的时候滑动主要就靠这个类。郭神的。医生的,还有爱哥的这些书中都有讲到。博客也有很多。它需要结合View的computeScroll()方法实现。
4. 自己实现一个简易的“动画驱动”
既然有些需求用原有的方法难以实现或者实现起来不太合适,这个时候我们就需要自己动手了。因此,我也写了一个简易的“动画驱动”
自定义动画驱动
其实就是自己把 path 的那些 moveTo ,lineTo 这些方法封装了下。
PathPoint类
详细代码如下:
public class PathPoint {
//移动指令
public static final int MOVE = 0;
//直线运动
public static final int LINE = 1;
//贝塞尔曲线
public static final int CURVE = 2;
//当前指令
int mOperation;
float mX;
float mY;
float mControl0X, mControl1X;//2个拐点
float mControl0Y, mControl1Y;
private PathPoint(int operation, float x, float y) {
mOperation = operation;
mX = x;
mY = y;
}
private PathPoint(float c0x, float c0y, float c1x, float c1y, float x, float y) {
mOperation = CURVE;
//终点
mX = x;
mY = y;
mControl0X = c0x;
mControl0Y = c0y;
mControl1X = c1x;
mControl1Y = c1y;
}
/**
* 移动
*
* @param x
* @param y
* @return
*/
public static PathPoint moveTo(float x, float y) {
return new PathPoint(MOVE, x, y);
}
/**
* 直线
*
* @param x
* @param y
* @return
*/
public static PathPoint lineTo(float x, float y) {
return new PathPoint(LINE, x, y);
}
/**
* 贝塞尔曲线
*
* @return
*/
public static PathPoint curveTo(float c0x, float c0y, float c1x, float c1y, float x, float y) {
return new PathPoint(c0x, c0y, c1x, c1y, x, y);
}
}
AnimatorPath类
public class AnimatorPath {
//存储路径集合
ArrayList<PathPoint> mPoints = new ArrayList<>();
/**
* 移动到哪个位置
*
* @param x
* @param y
*/
public void moveTo(float x, float y) {
mPoints.add(PathPoint.moveTo(x, y));
}
//直线
public void lineTo(float x, float y) {
mPoints.add(PathPoint.lineTo(x, y));
}
//贝塞尔曲线
public void curveTo(float c0x, float c0y, float c1x, float c1y, float x, float y) {
mPoints.add(PathPoint.curveTo(c0x, c0y, c1x, c1y, x, y));
}
public Collection<PathPoint> getPoints() {
return mPoints;
}
public void clear() {
if (mPoints != null && mPoints.size() > 0) {
mPoints.clear();
}
}
}
PathEvaluator类 主要实现了move line 贝塞尔曲线这些方法
public class PathEvaluator implements TypeEvaluator<PathPoint> {
/**
* @param t 动画执行的百分比 ,其实就是时间
* @param startValue
* @param endValue
* @return
*/
@Override
public PathPoint evaluate(float t, PathPoint startValue, PathPoint endValue) {
//进行估值
float x, y;
//判断进行哪种运动
if (endValue.mOperation == PathPoint.CURVE) {
//贝塞尔曲线方式
float oneMinusT = 1 - t;
//x的实时坐标
x = oneMinusT * oneMinusT * oneMinusT * startValue.mX +
3 * oneMinusT * oneMinusT * t * endValue.mControl0X +
3 * oneMinusT * t * t * endValue.mControl1X +
t * t * t * endValue.mX;
//y的实时坐标
y = oneMinusT * oneMinusT * oneMinusT * startValue.mY +
3 * oneMinusT * oneMinusT * t * endValue.mControl0Y +
3 * oneMinusT * t * t * endValue.mControl1Y +
t * t * t * endValue.mY;
} else if (endValue.mOperation == PathPoint.LINE) {
//直线运动方式
//当前坐标点(x,y) = 起始点 +t*起始点和终点的距离
x = startValue.mX + t * (endValue.mX - startValue.mX);
y = startValue.mY + t * (endValue.mY - startValue.mY);
} else {
//moveto方式
x = endValue.mX;
y = endValue.mY;
}
//不断的把控制点 move到 x,y的位置
return PathPoint.moveTo(x, y);
}
}
Activity类
public class BezierActivity extends AppCompatActivity {
private static final long ANIMATION_DUARTION = 400;//运动时间
private static final float MINIMUN_X_DISTANCE = 200;//x轴运动距离
private static final float SCALE_FACTOR_EXPAND = 13;//扩大倍数
private static final float SCALE_FACTOR_ORI = 1;//最初的倍数
ImageButton mFab;
FrameLayout mFabContainer;//帧布局
LinearLayout mControlsContainer;
private int mFabSize;//ImageButton的大小
private boolean mRevealFlag;
private boolean mResetFlag;
private float startX;
private float startY;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.activity_bezier);
mFabSize = getResources().getDimensionPixelSize(R.dimen.fab_size);
bindViews();
}
private void bindViews() {
mFab = (ImageButton) findViewById(R.id.fab);
mFab.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
onFabPressed(v);
}
});
mFabContainer = (FrameLayout) findViewById(R.id.fab_container);
mControlsContainer = (LinearLayout) findViewById(R.id.media_controls_container);
}
public void onFabPressed(View view) {
//还没运动前的X坐标
startX = mFab.getX();
startY = mFab.getY();
//开启动画 使用属性动画 属性动画控制对象身上的任何属性值 (必须有set方法)
AnimatorPath mPath = new AnimatorPath();
mPath.moveTo(0, 0);
//贝塞尔曲线
mPath.curveTo(-200, 200, -400, 100, -600, 0);
//相对于原来移动的点
//填this是控制当前对象(当前是BezierActivity)的PathPoint p 这个属性 。 mPath.getPoints()是相当于从某个值到某个值
ObjectAnimator animator = ObjectAnimator.ofObject(this, "fabLocation", new PathEvaluator(), mPath.getPoints().toArray());
animator.setDuration(ANIMATION_DUARTION);
//设置加速
animator.start();
//水波纹效果
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
//X轴运动距离超过200 开始水波纹扩散
if (Math.abs(startX - mFab.getX()) > MINIMUN_X_DISTANCE) {
if (!mRevealFlag) {
//高版本 api 运动
//已经设置成透明的了。设置回来
mFab.setImageDrawable(new BitmapDrawable());
mFabContainer.setY(mFabContainer.getY() + mFabSize / 2);
mFab.animate()
.scaleX(SCALE_FACTOR_EXPAND)
.scaleY(SCALE_FACTOR_EXPAND)
.setListener(endListener)
.setDuration(ANIMATION_DUARTION)
.start();
mRevealFlag = true;
mResetFlag = false;
}
}
}
});
}
//反射 不用直接设置属性
PathPoint fabLocation;
public void setFabLocation(PathPoint fabLocation) {
//达到不断的控制view进行移动
mFab.setTranslationX(fabLocation.mX);
mFab.setTranslationY(fabLocation.mY);
}
//开始动画。效果跟点击开始按钮一样
public void startAnimator(View view) {
onFabPressed(view);
}
/**
* fab做正向移动时的listerner
*/
private AnimatorListenerAdapter endListener = new AnimatorListenerAdapter() {
@Override
public void onAnimationCancel(Animator animation) {
super.onAnimationCancel(animation);
}
@Override
public void onAnimationEnd(Animator animation) {
mFab.setVisibility(View.INVISIBLE);
mFabContainer.setBackgroundColor(getResources().getColor(R.color.brand_accent));
for (int i = 0; i < mFabContainer.getChildCount(); i++) {
View v = mControlsContainer.getChildAt(i);
ViewPropertyAnimator animator = v.animate().scaleX(1).scaleY(1).setDuration(ANIMATION_DUARTION);
//依次显示
animator.setStartDelay(i * 50).start();
}
}
};
/**
* 重置动画 1.水波纹
* 2. framelayout布局上移
* 3. fab做位移
*
* @param view
*/
public void reset(View view) {
//1.先隐藏
for (int i = 0; i < mFabContainer.getChildCount() + 1; i++) {
View v = mControlsContainer.getChildAt(i);
ViewPropertyAnimator animator = v.animate().scaleX(0).scaleY(0).setDuration(ANIMATION_DUARTION);
//依次显示
animator.setStartDelay(i * 50);
//最后一个动画的时候监听动画结束 ,开始显示fab。然后进行缩放
if (i == mControlsContainer.getChildCount() - 1) {
animator.setListener(reverseListener);
}
animator.start();
}
AnimatorPath mPath1 = new AnimatorPath();
mPath1.moveTo(-600, 0);
mPath1.lineTo(0, 0);
//相对于原来移动的点
//填this是控制当前对象(当前是BezierActivity)的PathPoint p 这个属性 。 mPath.getPoints()是相当于从某个值到某个值
ObjectAnimator fabLocation = ObjectAnimator.ofObject(this, "fabLocation", new PathEvaluator(), mPath1.getPoints().toArray());
fabLocation.setDuration(ANIMATION_DUARTION);
//设置加速
fabLocation.start();
}
/**
* 2.结束的时候fab显示。然后进行缩放
*/
private AnimatorListenerAdapter reverseListener = new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
//结束的时候背景显示成透明
mFabContainer.setBackgroundColor(getResources().getColor(android.R.color.transparent));
//ImageButton设置成显示状态
mFab.setVisibility(View.VISIBLE);
//2. ImageButton开始缩放 从13-1的缩放
PropertyValuesHolder scaleX = PropertyValuesHolder.ofFloat("scaleX", 13, 1);
PropertyValuesHolder scaleY = PropertyValuesHolder.ofFloat("scaleY", 13, 1);
ObjectAnimator objectAnimator = ObjectAnimator.ofPropertyValuesHolder(mFab, scaleX, scaleY);
objectAnimator.setDuration(ANIMATION_DUARTION);
objectAnimator.addListener(listenerAdapter);
objectAnimator.start();
}
};
private AnimatorListenerAdapter listenerAdapter = new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
}
@Override
public void onAnimationEnd(Animator animation) {
//ImageButton缩放动画结束的时候
if (!mResetFlag) {
mFabContainer.setY(mFabContainer.getY() - mFabSize / 2);
mFab.setImageBitmap(BitmapFactory.decodeResource(getResources(), android.R.drawable.ic_media_pause));
mResetFlag = true;
mRevealFlag = false;
}
}
};
}
同时上传到github:https://github.com/Xiemarc/DesignPatterns
猜你喜欢
- 2024-10-24 初探animation中steps()属性(animation steps属性)
- 2024-10-24 HTML5(九)——超强的 SVG 动画(htmlsvg动画代码)
- 2024-10-24 自定义日历(二)(自定义日历控件)
- 2024-10-24 Flutter简单动画Animation运用(flutter 视频教程)
- 2024-10-24 css3中动画animation中的steps()函数
- 2024-10-24 移动端渲染原理浅析(移动端渲染原理浅析设计)
- 2024-10-24 iOS 事件处理机制与图像渲染过程(简述ios中的事件响应机制)
- 2024-10-24 Android 开机问题分析(android无法开机)
- 2024-10-24 GoogleCTF + zer0ptsCTF + ImaginaryCTF 2023 笔记
- 2024-10-24 决战“金三银四”,中高级Web前端大厂面试秘籍:CSS篇
- 11-26Win7\8\10下一条cmd命令可查得笔记本电脑连接过的Wifi密码
- 11-26一文搞懂MySQL行锁、表锁、间隙锁详解
- 11-26电脑的wifi密码忘记了?一招教你如何找回密码,简单明了,快收藏
- 11-26代码解决忘记密码问题 教你用CMD命令查看所有连接过的WIFI密码
- 11-26CMD命令提示符能干嘛?这些功能你都知道吗?
- 11-26性能测试之慢sql分析
- 11-26论渗透信息收集的重要性
- 11-26如何查看电脑连接过的所有WiFi密码
- 最近发表
- 标签列表
-
- cmd/c (57)
- c++中::是什么意思 (57)
- sqlset (59)
- ps可以打开pdf格式吗 (58)
- phprequire_once (61)
- localstorage.removeitem (74)
- routermode (59)
- vector线程安全吗 (70)
- & (66)
- java (73)
- org.redisson (64)
- log.warn (60)
- cannotinstantiatethetype (62)
- js数组插入 (83)
- resttemplateokhttp (59)
- gormwherein (64)
- linux删除一个文件夹 (65)
- mac安装java (72)
- reader.onload (61)
- outofmemoryerror是什么意思 (64)
- flask文件上传 (63)
- eacces (67)
- 查看mysql是否启动 (70)
- java是值传递还是引用传递 (58)
- 无效的列索引 (74)