优秀的编程知识分享平台

网站首页 > 技术文章 正文

Android实现贝塞尔曲线(android贝塞尔曲线渐变)

nanyue 2024-10-24 11:50:06 技术文章 4 ℃

前言

android开发中动画有多么重要,相信大家都清楚。它可以让一个枯燥乏味的静态界面变成一个充满动力的动画世界,提高用户体验。反正现在都是用户体验至上。android也是前端。

废话不多少。直接上效果图

主要就是中间那部分的动画效果。

理解Android中动画实现的本质

在理解Android中动画实现的本质之前,首先要理解动画实现的原理,估计这个大家都清楚。

如果要在Android中实现动画展示,那么就必须要有一个“动画驱动”每隔1/24秒去调用View的draw()方法,同时改变每一帧中View需要变化的元素,让这个View不断的绘制,这样一来,所有变化就是组合成一个流畅的动画。

上面就是“Android中动画实现的本质”,其关键就是要有一个“动画驱动”。回想下我们平时最常用的动画类Animation或者Animator,其实它们内部实现也是一个“动画驱动”,驱动View不断绘制。所以,我们完全可以不用Animation或者Animator去做动画,只要有一个“驱动”即可,例如Scroller是个不错的选择,甚至我们可以写一个我们自己实现的“动画驱动”。

常用的“动画驱动”

  1. 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

Tags:

最近发表
标签列表