可以看一下和了解一下贝塞尔曲线:http://www.html-js.com/article/1628
简单使用
绘制一阶贝塞尔曲线
Paint paint=new Paint();
paint.setDither(true);
paint.setAntiAlias(true);
paint.setStyle(Paint.Style.STROKE);
paint.setColor(Color.BLACK);
canvas.drawLine(0,mHeight/3,mWidth,mHeight/3,paint);
canvas.drawLine(0,mHeight*2/3,mWidth,mHeight*2/3,paint);
//1.绘制一阶贝塞尔曲线
paint.setColor(Color.RED);
Path path=new Path();
path.moveTo(100,100);
path.lineTo(900,500);
canvas.drawPath(path,paint);
绘制二阶贝赛尔曲线,主要用quadTo这个方法
//2.绘制二阶贝赛尔曲线 quadTo
paint.setColor(Color.GREEN);
Path path2=new Path();
path2.moveTo(100,800);
path2.quadTo(300,600,mWidth-50,800);
canvas.drawPath(path2,paint);
//绘制控制台
canvas.drawCircle(300,600,5,paint);
绘制三阶贝赛尔曲线主要用cubicTo
//3.绘制三阶贝赛尔曲线,cubicTo
paint.setColor(Color.BLUE);
Path path3=new Path();
path3.moveTo(100,1200);
path3.cubicTo(300,1100,900,1500,mWidth-100,1200);
canvas.drawPath(path3,paint);
//绘制控制台
canvas.drawCircle(300,1100,5,paint);
canvas.drawCircle(900,1500,5,paint);
实战一:模仿qq会员等级
效果图.png代码其实也比较简单,主要用到贝赛尔曲线公式,这里我将贝赛尔曲线2和3阶公式封装成一个工具类
public class BezierUtil {
/**
* B(t)=(1-t)^2*p0+2t*(1-t)*p1+t^2*p2,t∈[0,1]
*
* @param t 力度步长
* @param p0 起始点
* @param p1 控制点
* @param p2 终止点
* @return t对应的点
*/
public static PointF getPointFromQuadBezier(float t, PointF p0, PointF p1, PointF p2) {
PointF pointF=new PointF();
float temp=1-t;
pointF.x=temp*temp*p0.x+temp*t*2*p1.x+t*t*p2.x;
pointF.y=temp*temp*p0.y+temp*t*2*p1.y+t*t*p2.y;
return pointF;
}
/**
* B(t)=(1-t)^3*p0+3*p1*t*(1-t)^2+3*p2*t^2*(1-t)+p3*t^3,t∈[0,1]
*
* @param t 力度步长
* @param point0 起始点
* @param point1 控制点1
* @param point2 控制点2
* @param point3 结束点
* @return t对应的点
*/
public static PointF getPointFromCubicBezier(float t, PointF point0, PointF point1, PointF point2,PointF point3){
PointF pointF=new PointF();
pointF.x = point0.x*(1-t)*(1-t)*(1-t)
+ 3*point1.x*t*(1-t)*(1-t)
+ 3*point2.x*t*t*(1-t)
+ point3.x*t*t*t;
pointF.y = point0.y*(1-t)*(1-t)*(1-t)
+ 3*point1.y*t*(1-t)*(1-t)
+ 3*point2.y*t*t*(1-t)
+ point3.y*t*t*t;
return pointF;
}
}
具体实现我直接贴代码,因为还比较简单,相信大家看看就会明白
public class BezierLayout extends RelativeLayout {
public int ImageWidth = 98;
public int ImageHeight = 98;
private PointF _pStart, _pControl, _pEnd; // 开始点坐标、控制点坐标,结束点坐标
private ImageView mIvQQ, mIvVIP, mIvYVIP, mIvYSVIP; // 4个qq等级的图片
private PointF _pQQ, _pVIP, _pYVIP, _pYSVIP; // 4个曲线上的点
public BezierLayout(Context context) {
this(context, null);
}
public BezierLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public BezierLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
//添加四个图片
private void init() {
mIvQQ = new ImageView(getContext());
mIvQQ.setImageResource(R.drawable.qq);
RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT);
params.width = ImageWidth;
params.height = ImageHeight;
mIvQQ.setLayoutParams(params);
this.addView(mIvQQ);
mIvVIP = new ImageView(getContext());
mIvVIP.setImageResource(R.drawable.vip);
RelativeLayout.LayoutParams params2 = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT);
params2.width = ImageWidth;
params2.height = ImageHeight;
mIvVIP.setLayoutParams(params2);
this.addView(mIvVIP);
mIvYVIP = new ImageView(getContext());
mIvYVIP.setImageResource(R.drawable.yvip);
RelativeLayout.LayoutParams params3 = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT);
params3.width = ImageWidth;
params3.height = ImageHeight;
mIvYVIP.setLayoutParams(params3);
this.addView(mIvYVIP);
mIvYSVIP = new ImageView(this.getContext());
mIvYSVIP.setImageResource(R.drawable.ysvip);
RelativeLayout.LayoutParams params4 = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT);
params4.width = ImageWidth;
params4.height = ImageHeight;
mIvYSVIP.setLayoutParams(params4);
this.addView(mIvYSVIP);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
_pStart = new PointF(0, h * 4 / 5);
_pControl = new PointF(w * 3 / 4, h * 3 / 4);
_pEnd = new PointF(w, h / 4);
_pQQ = BezierUtil.getPointFromQuadBezier(0.15f, _pStart, _pControl, _pEnd);
_pVIP = BezierUtil.getPointFromQuadBezier(0.15f + 0.23f, _pStart, _pControl, _pEnd);
_pYVIP = BezierUtil.getPointFromQuadBezier(0.85f - 0.23f, _pStart, _pControl, _pEnd);
_pYSVIP = BezierUtil.getPointFromQuadBezier(0.85f, _pStart, _pControl, _pEnd);
((RelativeLayout.LayoutParams) mIvQQ.getLayoutParams()).leftMargin = (int) _pQQ.x - ImageWidth / 2;
((RelativeLayout.LayoutParams) mIvQQ.getLayoutParams()).topMargin = (int) _pQQ.y - ImageWidth / 2;
((RelativeLayout.LayoutParams) mIvVIP.getLayoutParams()).leftMargin = (int) _pVIP.x - ImageWidth / 2;
((RelativeLayout.LayoutParams) mIvVIP.getLayoutParams()).topMargin = (int) _pVIP.y - ImageWidth / 2;
((RelativeLayout.LayoutParams) mIvYVIP.getLayoutParams()).leftMargin = (int) _pYVIP.x - ImageWidth / 2;
((RelativeLayout.LayoutParams) mIvYVIP.getLayoutParams()).topMargin = (int) _pYVIP.y - ImageWidth / 2;
((RelativeLayout.LayoutParams) mIvYSVIP.getLayoutParams()).leftMargin = (int) _pYSVIP.x - ImageWidth / 2;
((RelativeLayout.LayoutParams) mIvYSVIP.getLayoutParams()).topMargin = (int) _pYSVIP.y - ImageWidth / 2;
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
Paint paint = new Paint();
//设置抗锯齿
paint.setAntiAlias(true);
paint.setStyle(Paint.Style.STROKE);
paint.setColor(0xFF33ccff);
paint.setStrokeWidth(10);
// 绘制二阶贝塞尔曲线
Path path = new Path();
path.moveTo(_pStart.x, _pStart.y);
path.quadTo(_pControl.x, _pControl.y, _pEnd.x, _pEnd.y);
canvas.drawPath(path, paint);
canvas.drawCircle(_pQQ.x, _pQQ.y, 10, paint);
canvas.drawCircle(_pVIP.x, _pVIP.y, 10, paint);
canvas.drawCircle(_pYVIP.x, _pYVIP.y, 10, paint);
canvas.drawCircle(_pYSVIP.x, _pYSVIP.y, 10, paint);
}
}
实战二
直接看效果
GIF.gif
这里为了看的更仔细没有去掉贝塞尔曲线
布局很简单,直接贴了
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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:id="@+id/rootlayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".AnimatorActivity">
<com.peakmain.bicyclesharing.view.MySky
android:id="@+id/sky"
android:layout_width="match_parent"
android:layout_height="175dp"
android:scaleType="fitXY"
app:srcCompat="@drawable/sky" />
<ImageView
android:id="@+id/skyline"
android:layout_width="match_parent"
android:layout_height="80dp"
android:layout_alignBottom="@+id/sky"
android:scaleType="fitXY"
app:srcCompat="@drawable/skyline" />
<ImageView
android:id="@+id/mountain"
android:layout_width="match_parent"
android:layout_height="63dp"
android:layout_alignBottom="@+id/sky"
android:scaleType="fitXY"
app:srcCompat="@drawable/mountain" />
<ImageView
android:id="@+id/land"
android:layout_width="match_parent"
android:layout_height="43dp"
android:layout_alignBottom="@+id/sky"
android:scaleType="fitXY"
app:srcCompat="@drawable/land" />
<ImageView
android:id="@+id/airport"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_marginTop="-25dp"
android:layout_marginLeft="25dp"
android:layout_below="@+id/sky"
app:srcCompat="@drawable/airport" />
<com.peakmain.bicyclesharing.view.MyPlane
android:id="@+id/plane"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_marginTop="-15dp"
android:layout_marginLeft="37dp"
android:layout_below="@+id/sky"
app:srcCompat="@drawable/plane" />
<ImageView
android:id="@+id/star"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_marginLeft="150dp"
android:layout_marginTop="15dp"
app:srcCompat="@drawable/ic_star_black_24dp"
tools:ignore="VectorDrawableCompat" />
<com.peakmain.bicyclesharing.view.MyBezierView
android:id="@+id/bezier"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#22FFFFFF"
/>
</RelativeLayout>
首先:MySky是一个自定义view,作用是在下拉的时候改变图片的高度和山的高度以及释放的时候回到原来的高度
public class MySky extends AppCompatImageView {
private float mOriginHeight;
private float mPercent;
private ImageView mSkylineIV;
private ImageView mMountainIV;
private ImageView mLandIV;
private ValueAnimator mValueAnimator;
public MySky(Context context) {
super(context);
}
public MySky(Context context, AttributeSet attrs) {
super(context, attrs);
init(attrs);
}
public MySky(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(attrs);
}
private void init(AttributeSet attrs) {
//获得原始的高度
String s = attrs.getAttributeValue(2).split("\\.")[0];
mOriginHeight = Integer.parseInt(s);
mOriginHeight = DensityUtil.dip2px(getContext(), mOriginHeight);
}
public void setPercent(float percent) {
mPercent = percent;
//设置高度
setHeight(this, mOriginHeight + (int) (Constant.PULL_MAX * mPercent + 0.5f));
// 修改3个imageview的高度
setHeight(mSkylineIV, getHeight() * Constant.SKYLINE_SCALE);
setHeight(mMountainIV, getHeight() * Constant.MOUNTAIN_SCALE);
setHeight(mLandIV, getHeight() * Constant.LAND_SCALE);
}
//设置其他view
public void setViews(ImageView v1, ImageView v2, ImageView v3) {
mSkylineIV = v1;
mMountainIV = v2;
mLandIV = v3;
}
private void setHeight(ImageView imageView, float height) {
RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) imageView.getLayoutParams();
params.height = (int) height;
imageView.setLayoutParams(params);
}
//up释放的时候
public void release(){
if(mValueAnimator==null){
final ValueAnimator animator = ValueAnimator.ofFloat(mPercent,0f);
animator.setInterpolator(new OvershootInterpolator());
animator.setDuration(500);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
Object val = animator.getAnimatedValue();
if(val instanceof Float){
setPercent((Float)val);
}
}
});
mValueAnimator = animator;
}else{
mValueAnimator.cancel();
mValueAnimator.setFloatValues(mPercent,0f);
}
mValueAnimator.start();
}
}
MyBezierView:这个是用来绘制贝赛尔曲线的,一共三个贝赛尔曲线
- 绘制飞机起飞的时候的贝塞尔曲线
private void drawBeizerUp(Canvas canvas) {
// 绘制上升路径
Path path = new Path();
Paint paint = new Paint();
paint.setAntiAlias(true);
paint.setDither(true);
paint.setStyle(Paint.Style.STROKE);
paint.setColor(Color.RED);
paint.setStrokeWidth(3);
float startPointX = DensityUtil.dip2px(getContext(), 67);
float startPointY = DensityUtil.dip2px(getContext(), 175);
float endPointX = getWidth() - 100;
float endPointY = 50;
float controlPointX = 800;
float controlPointY = 400;
path.moveTo(startPointX, startPointY);
path.quadTo(controlPointX,controlPointY,endPointX,endPointY);
canvas.drawPath(path, paint);
canvas.drawCircle(startPointX, startPointY, 15, paint);
canvas.drawCircle(endPointX, endPointY, 15, paint);
canvas.drawCircle(controlPointX, controlPointY, 15, paint);
}
- 绘制向下和水平的贝赛尔曲线
private void drawBeizerDown(Canvas canvas) {
// 绘制下降路径
Path path = new Path();
Paint paint = new Paint();
paint.setAntiAlias(true);
paint.setDither(true);
paint.setStyle(Paint.Style.STROKE);
paint.setColor(Color.GREEN);
paint.setStrokeWidth(3);
float startPointX = getWidth() - 100;
float startPointY = 100;
float endPointX = 0;
float endPointY = DensityUtil.dip2px(getContext(), 175) -100;
float controlPointX = 540;
float controlPointY = 160;
path.moveTo(startPointX, startPointY);
path.quadTo(controlPointX, controlPointY, endPointX, endPointY);
canvas.drawPath(path, paint);
canvas.drawCircle(startPointX, startPointY, 15, paint);
canvas.drawCircle(endPointX, endPointY, 15, paint);
canvas.drawCircle(controlPointX, controlPointY, 15, paint);
}
private void drawBeizerLevel(Canvas canvas) {
Paint paint = new Paint();
paint.setAntiAlias(true);
paint.setDither(true);
paint.setStyle(Paint.Style.STROKE);
paint.setColor(Color.BLUE);
paint.setStrokeWidth(3);
float startPointY = DensityUtil.dip2px(getContext(),175);
float endPointX = DensityUtil.dip2px(getContext(),37);
canvas.drawLine(-100,startPointY,endPointX,startPointY,paint);
}
最后是MyPlane:这个就是绘制飞机的动画
- 第一步就是提供起飞前向下拉的时候有个角度变化的方法
public void setPercent(float percent) {
this.setRotation(-45 * percent);
}
- 起飞动画
/**
* 起飞动画
*/
private void upAnim() {
PointF start = new PointF(DensityUtil.dip2px(getContext(), 67),
DensityUtil.dip2px(getContext(), 175));
PointF end = new PointF(DensityUtil.getScreenWidth(getContext()) - 100,
50);
PointF control = new PointF(800, 400);
MyBezierEvaluator evaluator = new MyBezierEvaluator(control);
mAnimatorUp = ValueAnimator.ofObject(evaluator,
start, end);
mAnimatorUp.setDuration(1000);
mAnimatorUp.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float skyHeight = DensityUtil.dip2px(getContext(), 175);
float planeHeight = DensityUtil.dip2px(getContext(), 30);
PointF currentPoint = (PointF) animation.getAnimatedValue();
setLayoutParams((int) currentPoint.x - (int) planeHeight / 2,
(int) currentPoint.y - (int) planeHeight / 2 - (int) skyHeight);
// 计算角度
float angle=(float) Math.atan2(currentPoint.y - mOldPoint.y,
currentPoint.x - mOldPoint.x);
setRotation((float)(angle * 180 /Math.PI));
mOldPoint=currentPoint;
}
});
}
- 降落动画
public void downAnim() {
PointF start = new PointF(DensityUtil.getScreenWidth(getContext())-100,
100);
PointF end = new PointF(-200,
DensityUtil.dip2px(getContext(),175));
PointF control = new PointF(540, 160);
MyBezierEvaluator evaluator = new MyBezierEvaluator(control);
mAnimatorDown = ValueAnimator.ofObject(evaluator,
start, end);
mAnimatorDown.setDuration(1000);
mAnimatorDown.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
float skyHeight = DensityUtil.dip2px(getContext(), 175);
float planeHeight = DensityUtil.dip2px(getContext(), 30);
PointF currentPoint = (PointF) valueAnimator.getAnimatedValue();
setLayoutParams((int) currentPoint.x - (int) planeHeight / 2,
(int) currentPoint.y - (int) planeHeight / 2 - (int) skyHeight);
// 计算角度
float angle = (float)Math.atan2(currentPoint.y - mOldPoint.y,
currentPoint.x - mOldPoint.x);
setRotation((float)(angle * 180 /Math.PI));
mOldPoint = currentPoint;
}
});
mAnimatorDown.setInterpolator(new AccelerateDecelerateInterpolator());
}
- 水平滑行动画
public void levelAnim() {
PointF start = new PointF(-200,
DensityUtil.dip2px(getContext(),175));
float endPointX = DensityUtil.dip2px(getContext(),37+15);
PointF end = new PointF(endPointX,
DensityUtil.dip2px(getContext(),175));
PointF control = new PointF(0, DensityUtil.dip2px(getContext(),175));
MyBezierEvaluator evaluator = new MyBezierEvaluator(control);
mAnimatorLevel = ValueAnimator.ofObject(evaluator,
start, end);
mAnimatorLevel.setDuration(1000);
mAnimatorLevel.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
float skyHeight = DensityUtil.dip2px(getContext(), 175);
float planeHeight = DensityUtil.dip2px(getContext(), 30);
PointF currentPoint = (PointF) valueAnimator.getAnimatedValue();
setLayoutParams((int) currentPoint.x - (int) planeHeight / 2,
(int) currentPoint.y - (int) planeHeight / 2 - (int) skyHeight);
// 计算角度
float angle = (float)Math.atan2(currentPoint.y - mOldPoint.y,
currentPoint.x - mOldPoint.x);
setRotation((float)(angle * 180 /Math.PI));
mOldPoint = currentPoint;
}
});
mAnimatorLevel.setInterpolator(new AccelerateDecelerateInterpolator());
}
启动动画
private void setLayoutParams(int left, int top) {
RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) getLayoutParams();
params.leftMargin = left;
params.topMargin = top;
setLayoutParams(params);
}
public void startAnimations(){
mAnimatorSet = new AnimatorSet();
mAnimatorSet.play(mAnimatorDown).after(mAnimatorUp).before(mAnimatorLevel);
mAnimatorSet.start();
}
Constant类
public class Constant {
public static final int PULL_MAX = 1000;
public static final float SKYLINE_SCALE = 80 / 175.0f;
public static final float MOUNTAIN_SCALE = 63 / 175.0f;
public static final float LAND_SCALE = 43 / 175.0f;
}
最后在MainActivity中去设置下拉触摸事件和真正动画控制
- 下拉高度改变和释放时动画实现
private void initView() {
mSkyIV = findViewById(R.id.sky);
mStarIV = findViewById(R.id.star);
mPlaneIV = findViewById(R.id.plane);
mSkyIV.setViews((ImageView) findViewById(R.id.skyline),
(ImageView) findViewById(R.id.mountain),
(ImageView) findViewById(R.id.land));
RelativeLayout layout = findViewById(R.id.rootlayout);
layout.setOnTouchListener(new OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
int action = event.getActionMasked();
switch (action) {
case MotionEvent.ACTION_DOWN:
mDownY = event.getY();
return true;
case MotionEvent.ACTION_MOVE:
float moveY = event.getY();
if (moveY >= mDownY) {
//距离
float distance = moveY - mDownY;
float percent = distance > Constant.PULL_MAX ? 1 : distance / Constant.PULL_MAX;
mSkyIV.setPercent(percent);
mPlaneIV.setPercent(percent);
}
break;
case MotionEvent.ACTION_UP:
mSkyIV.release();
mPlaneIV.startAnimations();
break;
}
return false;
}
});
}
- 星星放大缩小动画
第一种写法
ObjectAnimator animator = ObjectAnimator.ofFloat(mStarIV, "scaleX", 1f, 1.5f, 0.8f, 1.0f);
animator.setDuration(2000).setRepeatCount(ValueAnimator.INFINITE);
animator.start();
ObjectAnimator animator2 = ObjectAnimator.ofFloat(mStarIV, "scaleY", 1f, 1.5f, 0.8f, 1f);
animator2.setDuration(2000).setRepeatCount(ValueAnimator.INFINITE);
animator2.start();
第二种写法
Path path = new Path();
path.moveTo(1, 1);
path.lineTo(1.5f, 1.5f);
path.lineTo(0.8f, 0.8f);
path.lineTo(1.0f, 1.0f);
//要求5.0以上
ObjectAnimator oa = ObjectAnimator.ofFloat(mStarIV, "scaleX", "scaleY", path).setDuration(2000);
oa.setRepeatCount(ValueAnimator.INFINITE);
oa.start();
第三种写法
ObjectAnimator animator1 = ObjectAnimator.ofFloat(mStarIV, "scaleX", 1f, 1.5f, 0.8f, 1f);
animator1.setDuration(2000).setRepeatCount(ValueAnimator.INFINITE);
ObjectAnimator animator2 = ObjectAnimator.ofFloat(mStarIV, "scaleY", 1f, 1.5f, 0.8f, 1f);
animator2.setDuration(2000).setRepeatCount(ValueAnimator.INFINITE);
AnimatorSet set = new AnimatorSet();
set.playTogether(animator1, animator2);
set.setDuration(2000);
set.start();
第四种写法
PropertyValuesHolder p1 = PropertyValuesHolder.ofFloat("scaleX", 1f, 1.5f, 0.8f, 1f);
PropertyValuesHolder p2 = PropertyValuesHolder.ofFloat("scaleY", 1f, 1.5f, 0.8f, 1f);
ObjectAnimator animator = ObjectAnimator.ofPropertyValuesHolder(mStarIV, p1, p2).setDuration(2000);
animator.setRepeatCount(ValueAnimator.INFINITE);
animator.start();
第五种写法
ScaleAnimation ta = new ScaleAnimation(1f, 1.5f, 1f, 1.5f,
Animation.RELATIVE_TO_SELF, 0.5f,
Animation.RELATIVE_TO_SELF, 0.5f);
ta.setRepeatCount(Animation.INFINITE);
ta.setDuration(2000);
网友评论