其他分享
首页 > 其他分享> > Android冒险之旅-23-自定义View--涟漪+分裂效果

Android冒险之旅-23-自定义View--涟漪+分裂效果

作者:互联网

效果图

 

说明

在之前的RippleView的基础上 增加了分裂效果 我称之为RippleView2

上一篇博客连接- -> RippleView

升级功能:

基础功能:

 代码

人狠话不多 注释超级详细

不要被长度吓到 开放性很高 所有属性基本都可以修改 因此GetSet方法很多

如果太长不好理解可以从我上一篇博客入手 那是基础版RippleView


/**
 * create by 星航指挥官
 * create on 2020/12/13
 * 不过是大梦一场空
 * 课不过是孤影照惊鸿
 */
//在RippleView的基础上 增加了分裂动作
public class RippleView2 extends View {
    //中心点坐标
    private float x = 0;
    private float y = 0;
    //是否需要中心圆  默认需要
    private boolean centry = true;
    //是否开启涟漪
    private boolean startRipple = true;
    //中心圆颜色
    private int centryColor = 0;
    //涟漪颜色
    private int rippleColor = 0;
    //中心圆半径,同时也是涟漪扩散初始半径的默认值
    private float minR = 25;
    //涟漪扩散最大半径默认值
    private float maxR = 100;
    //涟漪扩散一圈的时间默认值
    private float speed = 2;
    //涟漪扩散间距默认值
    private float spacing = 75;
    //是否在分裂状态
    private boolean onSplit = false;
    //分裂个数
    private int splitCount = 3;
    //涟漪集合
    private ArrayList<Ripple> ripples;
    //分裂对象集合
    private ArrayList<Split> splits;

    /*
     * 构造器
     * */
    public RippleView2(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        //初始化数组
        ripples = new ArrayList<>();
        splits = new ArrayList<>();
        //获取用户自定义属性
        TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.RippleView);
        //获取用户是否需要中心圆
        centry = ta.getBoolean(R.styleable.RippleView_centry, centry);
        //是否开启涟漪
        startRipple = ta.getBoolean(R.styleable.RippleView_startRipple, startRipple);
        //获取用户定义的涟漪最小半径
        minR = ta.getDimension(R.styleable.RippleView_minR, minR);
        //获取用户定义的涟漪扩散一圈时间
        speed = ta.getFloat(R.styleable.RippleView_speed, speed);
        //获取用户定义的涟漪扩散间隔
        spacing = ta.getDimension(R.styleable.RippleView_spacing, spacing);
        //中心圆颜色
        centryColor = ta.getColor(R.styleable.RippleView_centryColor, Color.parseColor("#FFA6A8"));
        //涟漪颜色
        rippleColor = ta.getColor(R.styleable.RippleView_rippleColor, Color.parseColor("#FFA6A8"));
        //资源回收
        ta.recycle();
    }

    //封装涟漪类
    static class Ripple {
        //涟漪半径
        private float r = 0;
        //涟漪画笔
        private Paint paint;
        //透明度
        private float alpha;
    }

    //封装分裂对象类
    static class Split {
        //x坐标
        private float x ;
        //y坐标
        private float y;
        //半径
        private float r;
        //x轴偏移量
        private float xs;
        //y轴偏移量
        private float ys;
        //画笔
        private Paint paint;
    }


    /*
     * 测量
     * */
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        //获取控件宽高的一半,即确定中心点
        x = MeasureSpec.getSize(widthMeasureSpec) / 2;
        y = MeasureSpec.getSize(heightMeasureSpec) / 2;
        //将宽高中的较小值作为涟漪最大半径
        maxR = x > y ? y : x;
    }

    /*
     * 绘制
     * */
    @Override
    protected void onDraw(Canvas canvas) {
        //调用父View的onDraw函数,因为View这个类帮我们实现了一些
        //基本的绘制功能,比如绘制背景颜色、背景图片等
        super.onDraw(canvas);
        //-------------中心圆---------------
        //判断用户是否需要中心圆 默认不需要
        if (centry) {
            Paint paint = new Paint();
            //画笔颜色
            paint.setColor(centryColor);
            //画笔样式 填充满
            paint.setStyle(Paint.Style.FILL);
            //画笔 抗锯齿
            paint.setAntiAlias(true);
            //绘制中心圆
            canvas.drawCircle(x, y, minR, paint);
        }

        //-------------涟  漪---------------
        //如果涟漪集合中一个涟漪都没有
        if (ripples.size() == 0 && startRipple) {
            //创建涟漪
            addRipple();
        }
        //如果涟漪存在 绘制涟漪
        if (ripples.size() != 0) {//先判空 避免涟漪被清除时空指针异常
            //遍历涟漪集合
            for (int i = 0; i < ripples.size(); i++) {
                //获取第i个涟漪对象
                Ripple ripple = ripples.get(i);
                //绘制涟漪
                canvas.drawCircle(x, y, ripple.r, ripple.paint);
            }
            //speed为一个涟漪扩散一圈的时间
            //由于一个涟漪遍历400次
            //为什么一个涟漪遍历400次请看 369行与370行
            //当然也可以直接修改删除涟漪的逻辑
            //那么handler的延时就应该为 speed*1000/400
            handler.sendEmptyMessageDelayed(0, (long) (speed * 1000 / 400));
        }
        //-------------分  裂---------------
        if (onSplit && splits.size() == 0) {
            //如果一个分裂对象都没有 那么创建分裂对象
            Log.e("TAG", "onDraw: 开始添加分裂对象");
            addSplit();
        }
        //如果分裂对象存在 绘制分裂对象
        if (splits.size() != 0) {//先判空 避免分裂对象被清除时空指针异常
            for (int i = 0; i < splits.size(); i++) {
                //获取第i个分裂对象
                Split split = splits.get(i);
                //绘制涟漪
                canvas.drawCircle(split.x, split.y, split.r, split.paint);
            }
            //如果分裂对象半径小于minR
            if (splits.get(0).r < minR)
                //这里handle的延时时间随意  但是越小越好
                //我懒得想其他的时间逻辑 继续使用上面涟漪扩散时间的逻辑
                handler.sendEmptyMessageDelayed(1, (long) (speed * 1000 / 400));
        }
    }


    /*
     * 添加涟漪
     * */
    private void addRipple() {
        //创建涟漪对象
        Ripple ripple = new Ripple();
        //设置初始半径
        ripple.r = minR;
        //创建画笔
        Paint paint = new Paint();
        //画笔颜色
        paint.setColor(rippleColor);
        //设置画笔风格为绘制边框
        paint.setStyle(Paint.Style.STROKE);
        //设置边框宽度为半径的四分之一
        paint.setStrokeWidth(ripple.r / 4);
        //抗锯齿
        paint.setAntiAlias(true);
        //将设置好的画笔交给对象
        ripple.paint = paint;
        //设置透明度
        ripple.alpha = 255f;
        //将涟漪对象添加到涟漪集合
        ripples.add(ripple);
    }


    /*
     * 添加分裂对象
     * */
    private void addSplit() {
   
        //清空分裂对象集合
        splits.clear();
        //创建分裂兑现
        Split split = new Split();
        switch (splitCount) {
     
            case 2:
                //         y轴
                //.........-..........          
                //.........-..........           
                //.........-..........           // O是中心点(x,y)
                //----#----O----#--------- x轴   // #是分裂目标点
                //.........-..........           //打算绘制20次
                //.........-..........          //偏移总长 x/2 或者 y/2
                //.........-..........          每次偏移量为(偏移总长/20) 即(x|y)/2/20
                //第一个
                split = new Split();
                //向左移动要减 所以加上负号
                split.xs = -x / 2 / 20;
                split.ys = 0;
                //添加到分裂对象集合
                splits.add(split);
                //第二个#
                split = new Split();
                split.xs = x / 2 / 20;
                split.ys = 0;
                //添加到分裂对象集合
                splits.add(split);
                break;
            case 3:
                //         y轴
                //.........-..........           
                //.........#..........           
                //.........-..........           // O是中心点(x,y) 
                //---------O-------------- x轴   // #是分裂目标点  
                //.........-..........           //打算绘制20次
                //....#....-....#.....           //偏移总长 x/2 或者 y/2
                //.........-..........           //每次偏移量为(偏移总长/20) 即(x|y)/2/20
                //第一个  正上方
                split = new Split();
                split.xs = 0;
                //向上移动要减 所以加上负号
                split.ys = -y / 2 / 20;
                //添加到分裂对象集合
                splits.add(split);
                //第二个  左边
                split = new Split();
                //向左移动要减 所以加上负号
                split.xs = -x / 2 / 20;
                split.ys = y / 2 / 20;
                //添加到分裂对象集合
                splits.add(split);
                //第三个   右边
                split = new Split();
                split.xs = x / 2 / 20;
                split.ys = y / 2 / 20;
                //添加到分裂对象集合
                splits.add(split);
                break;
            case 4:
                //         y轴
                //.........-..........           
                //....#....-....#.....           
                //.........-..........           // O是中心点(x,y) 
                //---------O-------------- x轴   // #是分裂目标点  
                //.........-..........           //打算绘制20次
                //....#....-....#.....           //偏移总长 x/2 或者 y/2
                //.........-..........           //每次偏移量为(偏移总长/20) 即(x|y)/2/20
                //第一个  左上
                split = new Split();
                //左和上都要加负号 其他同理
                split.xs = -x / 2 / 20;
                split.ys = -y / 2 / 20;
                //添加到分裂对象集合
                splits.add(split);
                //第二个   右上
                split = new Split();
                split.xs = x / 2 / 20;
                split.ys = -y / 2 / 20;
                //添加到分裂对象集合
                splits.add(split);
                //第三个  左下
                split = new Split();
                split.xs = -x / 2 / 20;
                split.ys = y / 2 / 20;
                //添加到分裂对象集合
                splits.add(split);
                //第四个   右下
                split = new Split();
                split.xs = x / 2 / 20;
                split.ys = y / 2 / 20;
                //添加到分裂对象集合
                splits.add(split);
                break;
        }
        //创建画笔
        Paint paint = new Paint();
        //画笔颜色
        paint.setColor(centryColor);
        //画笔样式 填充满
        paint.setStyle(Paint.Style.FILL);
        //画笔 抗锯齿
        paint.setAntiAlias(true);
        //设置所有分裂对象的默认属性
        for (int i = 0; i <splits.size() ; i++) {
            splits.get(i).x=x;
            splits.get(i).y=y;
            splits.get(i).r=0;
            splits.get(i).paint=paint;
        }
    }

    /*
     * 刷新涟漪半径、透明度、宽度
     * */
    private void flushRipple() {
        for (int i = 0; i < ripples.size(); i++) {
            //获取第i个涟漪对象
            Ripple ripple = ripples.get(i);
            //如果第i个涟漪的半径加上边框大于最大半径 则删除涟漪
            if (ripple.r + ripple.r / 8 >= maxR) {
                //移除涟漪对象
                ripples.remove(i);
                //因为移除了一个对象 导致后续对象前移 i-- 确保完全遍历
                i--;
                //遍历下一个对象
                continue;
            }
            //透明度递减
            //由于一个涟漪遍历400次
            //为什么一个涟漪遍历400次请看 369行与370行
            //所以每次减少0.6375  一百次之后透明度为0
            ripple.alpha -= 0.6375f;
            //如果透明度<0  设置为0  防止负数
            if (ripple.alpha < 0)
                ripple.alpha = 0;
            //将递减的透明度设置给当前涟漪
            ripple.paint.setAlpha((int) ripple.alpha);
            //maxR-minR的值为涟漪可增长的长度
            //而涟漪设置了StrokeWidth
            //边框宽度固定为半径的四分之一 即r/4
            //所以涟漪的  实际半径=画笔半径+边框宽度/2=r+r/4/2 = r/8*9
            //那么 涟漪可增长的长度包括了涟漪半径与边框
            //因此我们要算出涟漪实际可以增长的半径为 (maxR-minR)/9*8
            //由于我们想要每个涟漪重绘400次 因此每次增加 可增长半径的1/400  即(maxR-minR)/9*8/400
            //绘制400次之后  涟漪半径就会因为超过最大半径而被删除
            ripple.r += (maxR - minR) * 8 / 9 / 400;
            ripple.paint.setStrokeWidth(ripple.r / 4);
        }
    }

    /*
     * 刷新分裂对象的位置、半径
     * */
    private void flushSplit() {
        for (int i = 0; i < splits.size(); i++) {
            //获取第一个分裂对象
            Split split = splits.get(i);
            //x坐标根据x轴偏移量偏移
            //由于偏移量在设置时自带符号 所以这里加就行
            split.x += split.xs;
            //y坐标根据y轴偏移量偏移
            //由于偏移量在设置时自带符号 所以这里加就行
            split.y += split.ys;
            //半径递增
            //从0增长至minR  增长20次  每次 minR / 20
            split.r += minR / 20;
        }
    }

    //内部Handler 实现涟漪周期荡漾
    private Handler handler = new Handler() {
        @Override
        public void handleMessage(@NonNull Message msg) {
            super.handleMessage(msg);
            switch (msg.what) {
                case 0:
                    //刷新涟漪
                    flushRipple();
                    if (ripples.size() != 0) {//先判空 避免涟漪被清除时空指针异常
                        //如果最内圈涟漪的半径大于初始半径+间隔半径,那么新增涟漪
                        if (ripples.get(ripples.size() - 1).r > minR + spacing && startRipple) {
                            addRipple();
                        }
                        //重新绘制
                        reDraw();
                    }
                    break;
                case 1:
                    //判断是否处于分裂状态
                    if (splits.size() != 0 && onSplit) {
                        //刷新分裂对象
                        flushSplit();
                        //重新绘制
                        reDraw();
                    }
                    break;
            }
        }

    };

    //-----------------------对外提供的Get、Set方法------------------------------------
    //查询是否存在中心圆
    public boolean isCentry() {
        return centry;
    }

    //设置是否有中心圆
    public void setCentry(boolean centry) {
        this.centry = centry;
        //刷新
        reDraw();
    }

    //查询是否开启涟漪
    public boolean isStartRipple() {
        return startRipple;
    }

    //设置是否开启涟漪
    public void setStartRipple(boolean startRipple) {
        //设置是否开启
        this.startRipple = startRipple;
        //刷新
        reDraw();
    }

    //获取中心圆颜色
    public int getCentryColor() {
        return centryColor;
    }

    //设置中心圆颜色
    public void setCentryColor(int centryColor) {
        this.centryColor = centryColor;
        //刷新
        reDraw();
    }

    //获取涟漪颜色
    public int getRippleColor() {
        return rippleColor;
    }

    //设置涟漪颜色
    public void setRippleColor(int rippleColor) {
        this.rippleColor = rippleColor;
        //刷新
        reDraw();
    }

    //获取涟漪最小半径,即中心圆半径
    public float getMinR() {
        return minR;
    }

    //设置涟漪最小半径,即中心圆半径
    public void setMinR(float minR) {
        this.minR = minR / 2;
        //刷新
        reDraw();
    }

    //获取涟漪最大半径
    public float getMaxR() {
        return maxR;
    }

    //设置涟漪最大半径
    public void setMaxR(float maxR) {
        this.maxR = maxR / 2;
        //刷新
        reDraw();
    }

    //获取涟漪荡漾速率
    public float getSpeed() {
        return speed;
    }

    //设置涟漪荡漾速率
    public void setSpeed(float speed) {
        this.speed = speed;
        //刷新
        reDraw();
    }

    //获取涟漪间距
    public float getSpacing() {
        return spacing;
    }

    //设置涟漪间距
    public void setSpacing(float spacing) {
        this.spacing = spacing;
        //刷新
        reDraw();
    }

    //开始分裂
    public void startSplit(int splitCount) {
        //清除分裂对象
        splits.clear();
        //关闭涟漪
        startRipple = false;
        //清除涟漪对象
        ripples.clear();
        //关闭中心圆
        centry = false;
        //开启分裂
        onSplit = true;
        //设置分裂个数
        if (splitCount == 2 || splitCount == 3 || splitCount == 4)
            this.splitCount = splitCount;
        //刷新
        reDraw();
    }

    //复原
    public void stopSplit() {
        //开启涟漪
        startRipple = true;
        //开启中心圆
        centry = true;
        //关闭分裂
        onSplit = false;
        //清空分裂集合
        splits.clear();
        //刷新
        reDraw();
    }

    //刷新View
    private void reDraw() {
        //清除handler的回调与Message 避免同时传递多个Message导致速率异常
        handler.removeCallbacksAndMessages(null);
        //同时重绘View
        invalidate();
    }
}

自定义属性

添加到res/values/attrs.xml里 没有就自己创建 当然你想放在strings.xml和styles.xml也行

    <declare-styleable name="RippleView">
        <!--中心圆半径与涟漪初始半径-->
        <attr name="minR" format="dimension"/>
        <!--涟漪扩散频率-->
        <attr name="speed" format="float"/>
        <!--涟漪间距-->
        <attr name="spacing" format="dimension"/>
        <!--是否需要中心圆-->
        <attr name="centry" format="boolean"/>
        <!--是否开启涟漪-->
        <attr name="startRipple" format="boolean"/>
        <!--中心圆颜色-->
        <attr name="centryColor" format="reference|color"/>
        <!--涟漪颜色-->
        <attr name="rippleColor" format="reference|color"/>
    </declare-styleable>

使用

xml中

    <qiyuan.lin.helloandroid.rippleview.RippleView2
        android:id="@+id/rippleview"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        app:centry="true"
        app:startRipple="true"
        app:centryColor="@color/mypink"
        app:spacing="100dp"
        app:speed="1"
        app:minR="25dp"
        app:rippleColor="@color/mypink" />

Activity中

        //绑定控件
        RippleView rippleView = findViewById(R.id.rippleview);
        //开启圆心
        rippleView.setCentry(true);
        //开启涟漪
        rippleView.setStartRipple(true);
        //设置圆心颜色
        rippleView.setCentryColor(Color.GREEN);
        //设置涟漪颜色
        rippleView.setRippleColor(Color.GREEN);
        //设置涟漪速率
        rippleView.setSpeed(1);
        //设置涟漪间距
        rippleView.setSpacing(100);
        //分裂2个
        rippleview.startSplit(2);
        //分裂3个
        rippleview.startSplit(3);
        //分裂4个
        rippleview.startSplit(4);
        //复原
        rippleview.stopSplit();

若有指点或者疑问,欢迎留言

END

标签:20,自定义,23,--,float,private,分裂,split,涟漪
来源: https://blog.csdn.net/AbyssskyLin/article/details/111326461