贝塞尔曲线
2016-07-17 本文已影响1524人
jacky123
-
二阶贝塞尔曲线
为了确定曲线上的一个点,需要进行两轮取点的操作,因此我们称得到的贝塞尔曲线为二次曲线.
-
使用三阶贝塞尔曲线模拟运动路径,三阶贝塞尔曲线公式如下
示例
1.利用三阶贝塞尔曲线模仿QQ空间直播时右下角的礼物冒泡特效 github:https://github.com/Yasic/QQBubbleView
sendflower.gif送花过程详解
1.第一过程
包括送花View(mFlowerImg)的移动,并且mFlowerImg的大小在移动过程中大小发生了改变。
//scale动画和贝塞尔曲线动画一起
ObjectAnimator scaleX = ObjectAnimator.ofFloat(mFlowerImg, "scaleX", 1.0f, 2f);
ObjectAnimator scaleY = ObjectAnimator.ofFloat(mFlowerImg, "scaleY", 1.0f, 2f);
AnimatorSet animSet = new AnimatorSet();
animSet.play(scaleX).with(scaleY).with(mValueAnimator);
animSet.setDuration(2000);
animSet.start();
2. 对贝塞尔曲线动画 mValueAnimator 初始化
private ValueAnimator mValueAnimator;
mValueAnimator = ValueAnimator.ofObject(new BezierEvaluator()
//第一个pointF:开始点,第二个PointF:终点
, new PointF(mWidthPixels, mHeightPixels), new PointF(mWidthPixels / 2, mHeightPixels / 2));
BezierEvaluator是一个PointF估值器:
class BezierEvaluator implements TypeEvaluator<PointF> {
@Override
public PointF evaluate(float fraction, PointF startValue, PointF endValue) {
float oneMinusT = 1.0f - fraction;
//startValue; //开始出现的点
//endValue; //结束终点
PointF controlPoint = new PointF(); //贝塞尔曲线控制点
controlPoint.set(mWidthPixels / 2 + 600, mHeightPixels / 2 - 300);
PointF point = new PointF(); //返回计算好的点
point.x = oneMinusT * oneMinusT * (startValue.x) + 2 * oneMinusT * fraction * (controlPoint.x) + fraction * fraction * (endValue.x);
point.y = oneMinusT * oneMinusT * (startValue.y) + 2 * oneMinusT * fraction * (controlPoint.y) + fraction * fraction * (endValue.y);
return point;
}
}
要想让花不断运动,就要实时设置它的移动位置,对mValueAnimator增加监听。
mValueAnimator.addUpdateListener(new AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
//根据计算好的点不断更新View的位置
PointF pointF = (PointF) animation.getAnimatedValue();
/**
* setX(float x) equals setTranslationX(x - mLeft);
* mLeft:Left position of this view relative to its parent.
*/
mFlowerImg.setX(pointF.x - mFlowerImg.getWidth() / 2);
mFlowerImg.setY(pointF.y - mFlowerImg.getHeight() / 2);
}
});
mValueAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
Log.i(TAG, "onAnimationStart");
mFlowerImg.setVisibility(View.VISIBLE);
}
@Override
public void onAnimationEnd(Animator animation) {
Log.i(TAG, "onAnimationEnd");
mFlowerImg.setVisibility(View.GONE);
mNumberImg.setVisibility(View.VISIBLE);
//数字加1的动画效果组合有:位移动画从指定坐标点移动到指定目标坐标点,并带有透明度变化的属性动画
PropertyValuesHolder xProperty = PropertyValuesHolder
.ofFloat("y", mHeightPixels / 2, mHeightPixels / 2 - 150f);//Y坐标轴:第二个参数是起始点,第三个是结束点坐标,下行X轴同理
PropertyValuesHolder yProperty = PropertyValuesHolder
.ofFloat("x", mWidthPixels / 2, mWidthPixels / 2);
PropertyValuesHolder alphaProperty = PropertyValuesHolder.ofFloat("alpha", 1f, 0.1f);//设置透明度的动画属性,过渡到0.1f透明度
//动画效果:目标View逐步变大,X轴和Y轴两个方向
PropertyValuesHolder scaleXProperty = PropertyValuesHolder.ofFloat("scaleX", 1.0f, 1.3f);
PropertyValuesHolder scaleYProperty = PropertyValuesHolder.ofFloat("scaleY", 1.0f, 1.3f);
ObjectAnimator animEnd = ObjectAnimator.ofPropertyValuesHolder(mNumberImg,
xProperty, yProperty, alphaProperty, scaleXProperty, scaleYProperty);//创建动画对象,把所有属性拼起来
animEnd.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
mNumberImg.setVisibility(View.GONE);
}
});
animEnd.setDuration(900).start();
}
});
说明:
1.在mValueAnimator的AnimatorUpdateListener中不断去重设mFlowerImg的位置。
2.在mValueAnimator的AnimatorListenerAdapter中监听ValueAnimator的状态。在动画结束之后,将花隐藏,将mNumberImg显示,并通过 PropertyValuesHolder 构造了操作一个对象的多个属性的ObjectAnimator,并启动它。
曲线显示过程
public class LandscapeBezierCurveView extends View {
private static final String TAG = LandscapeBezierCurveView.class.getSimpleName();
private Paint mPaint;
private Path mPath;
private int mWidth;
private int mHeight;
public LandscapeBezierCurveView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public LandscapeBezierCurveView(Context context) {
super(context);
init();
}
private void init() {
mPaint = new Paint();
mPaint.setColor(Color.RED);
mPaint.setAntiAlias(true);
mPaint.setStyle(Style.STROKE);
mPaint.setStrokeWidth(1);
mPath = new Path();
}
public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
mWidth = MeasureSpec.getSize(widthMeasureSpec);
mHeight = MeasureSpec.getSize(heightMeasureSpec);
Log.i(TAG, "width = " + mWidth + "| height = " + mHeight);
}
public void onDraw(Canvas canvas) {
canvas.drawColor(Color.TRANSPARENT);
mPath.reset();
mPath.moveTo(mWidth, mHeight); //开始起点
mPath.quadTo(mWidth / 2 + 600, mHeight / 2 - 300, mWidth / 2, mHeight / 2); // 控制点、终点
canvas.drawPath(mPath, mPaint);
}
}
使用:
showLine.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
lineView.setVisibility(lineView.isShown() ? View.GONE : View.VISIBLE);
}
});
拓展:
支持同时发送鲜花。
为每个View设置监听,并动态增加和减少ContentView的View个数
public class LandscapeActivity extends Activity {
protected static final String TAG = "LandscapeActivity";
private ImageView mNumberImg;
private int mWidthPixels;
private int mHeightPixels;
private ViewGroup parent;
private Bitmap bitmap;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
parent = (ViewGroup) LayoutInflater.from(this).inflate(R.layout.activity_landscape,null,false);
setContentView(parent);
// mFlowerImg = (ImageView) findViewById(R.id.flower);
mNumberImg = (ImageView) findViewById(R.id.number_im);
bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.icon_xiao_hua);
final Button sendFlowers = (Button) findViewById(R.id.send_flowers_bt);
final View lineView = findViewById(R.id.bezierView);
Button showLine = (Button) findViewById(R.id.show_bt);
showLine.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
lineView.setVisibility(lineView.isShown() ? View.GONE : View.VISIBLE);
}
});
sendFlowers.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
ImageView mFlowerImg = new ImageView(LandscapeActivity.this);
//设置标签,加入容器
// mFlowerImg.setTag(++count);
// imageViewList.add(mFlowerImg);
//将当前创建的ImageView加入到ContentView
parent.addView(mFlowerImg);
int bitmapW = bitmap.getWidth();
int bitmapH = bitmap.getHeight();
mFlowerImg.setLayoutParams(new FrameLayout.LayoutParams(bitmapW, bitmapH));
mFlowerImg.setImageBitmap(bitmap);
//scale动画和贝塞尔曲线动画一起
ObjectAnimator scaleX = ObjectAnimator.ofFloat(mFlowerImg, "scaleX", 1.0f, 2f);
ObjectAnimator scaleY = ObjectAnimator.ofFloat(mFlowerImg, "scaleY", 1.0f, 2f);
AnimatorSet animSet = new AnimatorSet();
animSet.play(scaleX).with(scaleY).with(new MyValueAnimator(mFlowerImg).get());
animSet.setDuration(2000);
animSet.start();
}
});
//获取屏幕宽高
mWidthPixels = getResources().getDisplayMetrics().widthPixels;
mHeightPixels = getResources().getDisplayMetrics().heightPixels;
Log.i(TAG, "width:" + mWidthPixels + " height:" + mHeightPixels);
}
@Override
protected void onStart() {
super.onStart();
}
class MyValueAnimator extends ValueAnimator{
private View view;
MyValueAnimator(View view){
this.view = view;
}
public ValueAnimator get(){
ValueAnimator myValueAnimator = ValueAnimator.ofObject(new BezierEvaluator()
//第一个pointF:开始点,第二个PointF:终点
, new PointF(mWidthPixels, mHeightPixels), new PointF(mWidthPixels / 2, mHeightPixels / 2));
//设置插值器——两边慢,中间快
myValueAnimator.setInterpolator(new AccelerateDecelerateInterpolator());
myValueAnimator.addUpdateListener(new AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
//根据计算好的点不断更新View的位置
PointF pointF = (PointF) animation.getAnimatedValue();
/**
* setX(float x) equals setTranslationX(x - mLeft);
* mLeft:Left position of this view relative to its parent.
*/
view.setX(pointF.x - view.getWidth() / 2);
view.setY(pointF.y - view.getHeight() / 2);
}
});
myValueAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
Log.i(TAG, "onAnimationStart");
view.setVisibility(View.VISIBLE);
}
@Override
public void onAnimationEnd(Animator animation) {
Log.i(TAG, "onAnimationEnd");
view.setVisibility(View.GONE);
parent.removeView(view);
mNumberImg.setVisibility(View.VISIBLE);
//数字加1的动画效果组合有:位移动画从指定坐标点移动到指定目标坐标点,并带有透明度变化的属性动画
PropertyValuesHolder xProperty = PropertyValuesHolder
.ofFloat("y", mHeightPixels / 2, mHeightPixels / 2 - 150f);//Y坐标轴:第二个参数是起始点,第三个是结束点坐标,下行X轴同理
PropertyValuesHolder yProperty = PropertyValuesHolder
.ofFloat("x", mWidthPixels / 2, mWidthPixels / 2);
PropertyValuesHolder alphaProperty = PropertyValuesHolder.ofFloat("alpha", 1f, 0.1f);//设置透明度的动画属性,过渡到0.1f透明度
//动画效果:目标View逐步变大,X轴和Y轴两个方向
PropertyValuesHolder scaleXProperty = PropertyValuesHolder.ofFloat("scaleX", 1.0f, 1.3f);
PropertyValuesHolder scaleYProperty = PropertyValuesHolder.ofFloat("scaleY", 1.0f, 1.3f);
ObjectAnimator animEnd = ObjectAnimator.ofPropertyValuesHolder(mNumberImg,
xProperty, yProperty, alphaProperty, scaleXProperty, scaleYProperty);//创建动画对象,把所有属性拼起来
animEnd.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
mNumberImg.setVisibility(View.GONE);
}
});
animEnd.setDuration(900).start();
}
});
return myValueAnimator;
}
}
class BezierEvaluator implements TypeEvaluator<PointF> {
@Override
public PointF evaluate(float fraction, PointF startValue, PointF endValue) {
float oneMinusT = 1.0f - fraction;
//startValue; //开始出现的点
//endValue; //结束终点
PointF controlPoint = new PointF(); //贝塞尔曲线控制点
controlPoint.set(mWidthPixels / 2 + 600, mHeightPixels / 2 - 300);
PointF point = new PointF(); //返回计算好的点
point.x = oneMinusT * oneMinusT * (startValue.x) + 2 * oneMinusT * fraction * (controlPoint.x) + fraction * fraction * (endValue.x);
point.y = oneMinusT * oneMinusT * (startValue.y) + 2 * oneMinusT * fraction * (controlPoint.y) + fraction * fraction * (endValue.y);
return point;
}
}
}
工具
在线模拟出想要的曲线
http://myst729.github.io/bezier-curve/