今天周六,难得不加班,而且又撞上这么好的天气,紫金山走一波,写博客就放到晚上吧。今天累够呛,以后还是要多爬爬山锻炼锻炼,做一只程序猿已经很惨了,做一只没有情调不懂生活的程序猿更惨,所以还是要想着去做一些有意思有意义的事情的。OK,忙里偷闲要搞事情了,看图。
啧啧,这就是我心中所想实现的效果的原型,然后再加点特效装饰一下,真香!来看下成品什么样子吧~
原gif图15M太大了上传不了,找了个压缩网站压的惨不忍睹......
OK,看实现步骤:
代码未动,思路先行
1.定义一个Path,根据事先模拟过的坐标点进行连接出胡萝卜的形状;
2.使用PathMeasure类对Path进行处理画出轨迹;
3.填充颜色:使用正弦函数公式从下到上进行填充,根据颜色不同增加判断处理。
一.模拟形状
这里我是根据View的尺寸对View进行分割和计算,得到我需要的坐标点。
/**
* 胡萝卜形状的Path,用来裁剪保留的区域
*/
private Path mPathCarrot = new Path();
/**
* PathMeasure测量类
*/
private PathMeasure measure = new PathMeasure();
============================赋值==============================
/**
* 添加一个圆弧
*/
mPathCarrot.addArc(new RectF(width / 2 - mCarrotWidth / 2, heightTop - 50
, width / 2 + mCarrotWidth / 2, heightTop + 50), 0, -180);
/**
* 胡萝卜的身体
*/
mPathCarrot.moveTo(width / 2 - mCarrotWidth / 2, heightTop);
mPathCarrot.lineTo(width / 2, height);
mPathCarrot.lineTo(width / 2 + mCarrotWidth / 2, heightTop);
/**
*左叶片
*/
mPathCarrot.moveTo(width / 2, heightTop - 50);
mPathCarrot.lineTo(0, mBladeLeftMaginTop);
mPathCarrot.lineTo(mBladeTopMaginLeft, 0);
mPathCarrot.lineTo(width / 2, heightTop - 50);
/**
*右叶片
*/
mPathCarrot.lineTo(width - mBladeTopMaginLeft, 0);
mPathCarrot.lineTo(width, mBladeLeftMaginTop);
mPathCarrot.lineTo(width / 2, heightTop - 50);
/**
* 把path放进PathMeasure
*/
measure.setPath(mPathCarrot, false);
OK,对Path事先处理之后就可以进行初步的绘制了,这里需要对canvas进行裁剪,代码如下:
canvas.clipPath(mPathCarrot);
canvas.drawPath(mPathCarrot, mPaintPath);
二.画出轨迹
这里使用到一个很重要的类就是PathMeasure,主要是对Path进行测量处理。刚才我们已经把mPathCarrot放进了measure中,接下来就可以对mPathCarrot进行处理了
/**
* 绘制轨迹路径
*
* @param canvas
*/
private void drawPath(Canvas canvas) {
float mCurrentLength = currentValue * measure.getLength();
measure.getSegment(0, mCurrentLength, dst, true);
canvas.drawPath(dst, mPaintTrajectory);
}
这个方法就是用来不停的对Path进行测量返回,然后就可以模拟出轨迹的效果了。我们刚才mPathCarrot分别添加了好几段进去,所以这里要对每一段进行处理,在处理完成一个片段之后要调用measure.nextContour();转移到下一个片段进行同样的处理,所以有了以下代码:
public void start() {
final ValueAnimator animator = ValueAnimator.ofFloat(0, 1);
animator.setRepeatCount(ValueAnimator.INFINITE);
animator.setRepeatMode(ValueAnimator.RESTART);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
currentValue = (float) valueAnimator.getAnimatedValue();
invalidate();
}
});
animator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationRepeat(Animator animation) {
super.onAnimationRepeat(animation);
measure.nextContour();
if (measure.getLength() == 0) {
animator.cancel();
}
}
@Override
public void onAnimationCancel(Animator animation) {
super.onAnimationCancel(animation);
startDrawBlade();
}
});
animator.setDuration(2000);
animator.start();
}
OK,轨迹绘制完成之后,接下来就是填充颜色,刚才的代码在绘制完成轨迹以后调用了animator.cancel();在onAnimationCancel方法回调中调用了startDrawBlade()方法,则开始填充颜色。
三.填充颜色
需要考虑的问题:
1.填充形式:结合正弦函数使用Path模拟水波纹效果进行填充,毕竟这样看起来比较炫酷而且自然,公式:y=A*sin(ωx+φ)+k
2.填充范围:因为使用了正弦函数,则需要考虑振幅的影响,那么范围就是[getHeight(),-2 * A]
注:getHeight()是View高度,A就是振幅。
3.填充颜色:胡萝卜橙色,叶片绿色。
代码如下:
/**
* 填充颜色
*/
private void fillCarrot(Canvas canvas, int type) {
φ -= mWaterSpeed / 100;
float y;
pathWater.reset();
pathWater.moveTo(0, mCurrentProgressY);
for (float x = 0; x <= getWidth(); x++) {
y = (float) (A * Math.sin(ω * x + φ) + K);
pathWater.lineTo(x, y + mCurrentProgressY);
}
//填充矩形
if (type == 0) {
pathWater.lineTo(getWidth(), getHeight() * 0.25f - 50);
pathWater.lineTo(0, getHeight() * 0.25f - 50);
pathWater.close();
canvas.drawPath(pathWater, mPaintBlade);
} else {
pathWater.lineTo(getWidth(), getHeight());
pathWater.lineTo(0, getHeight());
pathWater.close();
canvas.drawPath(pathWater, mPaintCarrot);
}
}
因为填充颜色也是一个循序渐进的过程,所以这里也有一个属性动画进行控制:
/**
* 填充叶片
* 该animator用来控制颜色填充区域的Y坐标值,范围[getHeight(),-2 * A]
* 总范围就是View高度加上振幅的2倍,因为这里是逐渐减小的,所以的-2A
*/
public void startDrawBlade() {
final ValueAnimator animator = ValueAnimator.ofFloat(getHeight(), -2 * A);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
mCurrentProgressY = (float) valueAnimator.getAnimatedValue();
invalidate();
}
});
animator.setDuration(6000);
animator.start();
}
绘制代码:
/**
* 判断当绘制到叶片开始的时候,进行不同的逻辑处理
*/
if (mCurrentProgressY <= getHeight() * 0.25f - 50) {
fillCarrot(canvas, 0);
/**
* 此时填充叶片颜色的时候,胡萝卜颜色使用RectF进行填充
*/
canvas.drawRect(mRectFCarrot, mPaintCarrot);
} else {
fillCarrot(canvas, 1);
}
OK,综上所述,就完成啦!!!
完整代码:
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PathMeasure;
import android.graphics.RectF;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.view.View;
import com.zhuyong.progressbar.ScreenUtils;
/**
* 一个可爱的胡萝卜控件
* 先绘制胡萝卜的轨迹,然后采用正弦函数的方式使用Path进行颜色填充
*/
public class CarrotView extends View {
/**
* 上下文
*/
private Context mContext;
/**
* 振幅
*/
private int A;
/**
* 偏距
*/
private int K;
/**
* 初相
*/
private float φ;
/**
* 角速度
*/
private double ω;
/**
* 波形移动的速度
*/
private float mWaterSpeed = 40f;
/**
* 绘制胡萝卜的形状
*/
private Paint mPaintPath;
/**
* 绘制胡萝卜的轨迹
*/
private Paint mPaintTrajectory;
/**
* 填充胡萝卜
*/
private Paint mPaintCarrot;
/**
* 填充胡萝卜叶片
*/
private Paint mPaintBlade;
/**
* 进度Y坐标
*/
private float mCurrentProgressY;
/**
* 用于纪录当前的位置,取值范围[0,1]映射Path的整个长
*/
private float currentValue = 0;
/**
* 胡萝卜形状的Path,用来裁剪保留的区域
*/
private Path mPathCarrot = new Path();
/**
* 水波纹Path
*/
private Path pathWater = new Path();
/**
* 绘制轨迹路径时保存长度的Path
*/
private Path dst = new Path();
/**
* PathMeasure测量类
*/
private PathMeasure measure = new PathMeasure();
/**
* 胡萝卜去除叶片以外区域的矩形框,用于绘制叶片颜色的时候保持胡萝卜橙色的颜色不变
*/
private RectF mRectFCarrot = new RectF();
/**
* 构造函数
*
* @param context
*/
public CarrotView(Context context) {
this(context, null);
}
public CarrotView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public CarrotView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mContext = context;
init();
}
/**
* 初始化
*/
private void init() {
/**
* 振幅
*/
A = ScreenUtils.dip2px(mContext, 5);
K = A;
mPaintTrajectory = new Paint();
mPaintTrajectory.setColor(Color.parseColor("#FF7F50"));
mPaintTrajectory.setStyle(Paint.Style.STROKE);
mPaintTrajectory.setAntiAlias(true);
mPaintTrajectory.setStrokeWidth(5);
mPaintPath = new Paint();
mPaintPath.setAntiAlias(true);
mPaintPath.setStyle(Paint.Style.FILL);
mPaintPath.setColor(Color.WHITE);
mPaintCarrot = new Paint();
mPaintCarrot.setColor(Color.parseColor("#FF7F50"));
mPaintCarrot.setAntiAlias(true);
mPaintCarrot.setStyle(Paint.Style.FILL);
mPaintBlade = new Paint();
mPaintBlade.setColor(Color.GREEN);
mPaintBlade.setAntiAlias(true);
mPaintBlade.setStyle(Paint.Style.FILL);
}
@Override
protected void onSizeChanged(int width, int height, int oldw, int oldh) {
super.onSizeChanged(width, height, oldw, oldh);
ω = 8 * Math.PI / width;
mCurrentProgressY = height;
/**
* 胡萝卜去除叶片以外区域的矩形框
*/
mRectFCarrot.set(0, height * 0.25f - 50, width, height);
/**
* 自定义一些方便控制胡萝卜形状的变量参数
*/
float mCarrotWidth = width * 0.4f;
float heightTop = height * 0.25f;
float mBladeLeftMaginTop = heightTop * 0.6f;//叶子左边的位置距离顶部的距离
float mBladeTopMaginLeft = width * 0.2f;//叶子上边的位置距离左边的距离
/**
* 添加一个圆弧
*/
mPathCarrot.addArc(new RectF(width / 2 - mCarrotWidth / 2, heightTop - 50
, width / 2 + mCarrotWidth / 2, heightTop + 50), 0, -180);
/**
* 胡萝卜的身体
*/
mPathCarrot.moveTo(width / 2 - mCarrotWidth / 2, heightTop);
mPathCarrot.lineTo(width / 2, height);
mPathCarrot.lineTo(width / 2 + mCarrotWidth / 2, heightTop);
/**
*左叶片
*/
mPathCarrot.moveTo(width / 2, heightTop - 50);
mPathCarrot.lineTo(0, mBladeLeftMaginTop);
mPathCarrot.lineTo(mBladeTopMaginLeft, 0);
mPathCarrot.lineTo(width / 2, heightTop - 50);
/**
*右叶片
*/
mPathCarrot.lineTo(width - mBladeTopMaginLeft, 0);
mPathCarrot.lineTo(width, mBladeLeftMaginTop);
mPathCarrot.lineTo(width / 2, heightTop - 50);
/**
* 把path放进PathMeasure
*/
measure.setPath(mPathCarrot, false);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
/**
* 裁剪绘制
*/
clipCicle(canvas);
/**
* 判断当绘制到叶片开始的时候,进行不同的逻辑处理
*/
if (mCurrentProgressY <= getHeight() * 0.25f - 50) {
fillCarrot(canvas, 0);
/**
* 此时填充叶片颜色的时候,胡萝卜颜色使用RectF进行填充
*/
canvas.drawRect(mRectFCarrot, mPaintCarrot);
} else {
fillCarrot(canvas, 1);
}
/**
* 绘制轨迹
*/
drawPath(canvas);
}
/**
* 填充颜色
*/
private void fillCarrot(Canvas canvas, int type) {
φ -= mWaterSpeed / 100;
float y;
pathWater.reset();
pathWater.moveTo(0, mCurrentProgressY);
for (float x = 0; x <= getWidth(); x++) {
y = (float) (A * Math.sin(ω * x + φ) + K);
pathWater.lineTo(x, y + mCurrentProgressY);
}
//填充矩形
if (type == 0) {
pathWater.lineTo(getWidth(), getHeight() * 0.25f - 50);
pathWater.lineTo(0, getHeight() * 0.25f - 50);
pathWater.close();
canvas.drawPath(pathWater, mPaintBlade);
} else {
pathWater.lineTo(getWidth(), getHeight());
pathWater.lineTo(0, getHeight());
pathWater.close();
canvas.drawPath(pathWater, mPaintCarrot);
}
}
/**
* 绘制轨迹路径
*
* @param canvas
*/
private void drawPath(Canvas canvas) {
float mCurrentLength = currentValue * measure.getLength();
measure.getSegment(0, mCurrentLength, dst, true);
canvas.drawPath(dst, mPaintTrajectory);
}
/**
* 裁剪画布为胡萝卜的形状
*
* @param canvas
*/
private void clipCicle(Canvas canvas) {
canvas.clipPath(mPathCarrot);
canvas.drawPath(mPathCarrot, mPaintPath);
}
public void start() {
final ValueAnimator animator = ValueAnimator.ofFloat(0, 1);
animator.setRepeatCount(ValueAnimator.INFINITE);
animator.setRepeatMode(ValueAnimator.RESTART);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
currentValue = (float) valueAnimator.getAnimatedValue();
invalidate();
}
});
animator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationRepeat(Animator animation) {
super.onAnimationRepeat(animation);
measure.nextContour();
if (measure.getLength() == 0) {
animator.cancel();
}
}
@Override
public void onAnimationCancel(Animator animation) {
super.onAnimationCancel(animation);
startDrawBlade();
}
});
animator.setDuration(2000);
animator.start();
}
/**
* 填充叶片
* 该animator用来控制颜色填充区域的Y坐标值,范围[getHeight(),-2 * A]
* 总范围就是View高度加上振幅的2倍,因为这里是逐渐减小的,所以的-2A
*/
public void startDrawBlade() {
final ValueAnimator animator = ValueAnimator.ofFloat(getHeight(), -2 * A);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
mCurrentProgressY = (float) valueAnimator.getAnimatedValue();
invalidate();
}
});
animator.setDuration(6000);
animator.start();
}
}
🥕🥕🥕happy