自定义View_做一个与众不同的🥕

今天周六,难得不加班,而且又撞上这么好的天气,紫金山走一波,写博客就放到晚上吧。今天累够呛,以后还是要多爬爬山锻炼锻炼,做一只程序猿已经很惨了,做一只没有情调不懂生活的程序猿更惨,所以还是要想着去做一些有意思有意义的事情的。OK,忙里偷闲要搞事情了,看图。



啧啧,这就是我心中所想实现的效果的原型,然后再加点特效装饰一下,真香!来看下成品什么样子吧~

是fu萝卜呀~

原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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,377评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,390评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,967评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,344评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,441评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,492评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,497评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,274评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,732评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,008评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,184评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,837评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,520评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,156评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,407评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,056评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,074评论 2 352