Android冒险之旅-24-自定义View--涟漪+分裂+自定义点击行为

RippleView3效果图

结合打印日志观看 用户点击中心圆输出0 点击分裂对象输出分裂对象对应序号 

说明

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

RippleView系列- ->RippleViewRippleView2RippleView3

RippleView3升级功能:

  • 中心圆点击监听                     被点击之后会自动分裂 同时标识:clickResult=0
  • 分裂对象点击监听                  被点击之后会复原到涟漪扩散状态  同时标识:clickResult=分裂对象序号
  • 提供clickResult的查询方法    用户为RippleView3添加点击事件后可以查询到clickResult进行相应处理(如效果图所示)

RippleView2升级功能:

  • 分裂两个    水平对称分布
  • 分裂三个    三角形分布
  • 分裂四个    对称分布
  • 复原           回到涟漪荡漾状态

RippleView基础功能:

  • 中心圆控制                    开启 / 关闭 / 修改颜色
  • 涟漪控制                       开启 / 关闭 / 修改颜色
  • 初始半径控制                涟漪初始大小与中心圆大小一致
  • 涟漪扩散速度控制         实际上就是一个涟漪从出现到消亡的时间 越小扩散越快
  • 涟漪扩散间距控制         就是指涟漪两两之间的间距
  • 绘制次数控制                 就是一个涟漪从出现到消亡被绘制的次数  相当于动画总帧数

代码


/**
 * create by 星航指挥官
 * create on 2020/12/13
 * 不过是大梦一场空
 * 课不过是孤影照惊鸿
 */
//在RippleView的基础上 增加了分裂动作
public class RippleView3 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 float drawCount = 400;
    //涟漪集合
    private ArrayList<Ripple> ripples;
    //分裂对象集合
    private ArrayList<Split> splits;
    //点击结果
    private int clickResult = -1;

    /*
     * 构造器
     * */
    public RippleView3(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        //初始化数组
        ripples = new ArrayList<>();
        splits = new ArrayList<>();
        //获取用户自定义属性
        TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.RippleView3);
        //获取用户是否需要中心圆
        centry = ta.getBoolean(R.styleable.RippleView3_centry, centry);
        //是否开启涟漪
        startRipple = ta.getBoolean(R.styleable.RippleView3_startRipple, startRipple);
        //获取用户定义的涟漪最小半径
        minR = ta.getDimension(R.styleable.RippleView3_minR, minR);
        //获取用户定义的涟漪扩散一圈时间
        speed = ta.getFloat(R.styleable.RippleView3_speed, speed);
        //获取用户定义的绘制次数
        drawCount = ta.getFloat(R.styleable.RippleView3_drawCount, drawCount);
        //获取用户定义的涟漪扩散间隔
        spacing = ta.getDimension(R.styleable.RippleView3_spacing, spacing);
        //中心圆颜色
        centryColor = ta.getColor(R.styleable.RippleView3_centryColor, Color.parseColor("#FFA6A8"));
        //涟漪颜色
        rippleColor = ta.getColor(R.styleable.RippleView3_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为一个涟漪扩散一圈的时间
            //由于一个涟漪遍历drawCount次
            //那么handler的延时就应该为 speed*1000/drawCount
            handler.sendEmptyMessageDelayed(0, (long) (speed * 1000 / drawCount));
        }
        //-------------分  裂---------------
        if (onSplit && splits.size() == 0) {
            //如果一个分裂对象都没有 那么创建分裂对象
            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 / drawCount));
        }
    }


    /*
     * 添加涟漪
     * */
    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;
            }
            //透明度递减
            //由于一个涟漪遍历drawCount次
            //所以每次减少255/drawCount次
            ripple.alpha -= 255 / drawCount;
            //如果透明度<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
            //由于我们想要每个涟漪重绘drawCount次 因此每次增加 可增长半径的1/drawCount  即(maxR-minR)/9*8/drawCount
            //绘制drawCount次之后  涟漪半径就会因为超过最大半径而被删除
            ripple.r += (maxR - minR) * 8 / 9 / drawCount;
            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;
            }
        }

    };

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                int touchx = (int) event.getX();
                int touchy = (int) event.getY();
                //如果不在分裂状态
                if (!onSplit) {
                    //如果点击的位置在中心圆里
                    if (isInCircle(x, y, minR, touchx, touchy)) {
                        //分裂
                        startSplit(splitCount);
                        clickResult = 0;
                    }else {
                        clickResult = -1;
                        return false;
                    }
                } else {
                    //遍历分裂对象 看点击的位置是否处于分裂对象中
                    for (int i = 0; i < splits.size(); i++) {
                        Split split = splits.get(i);
                        if (isInCircle(split.x, split.y, minR, touchx, touchy)){
                            clickResult = i+1;
                            stopSplit();
                        }
                    }
                    if (clickResult<1){
                        return false;
                    }
                }
                break;
            case MotionEvent.ACTION_UP:
                break;
        }
        return super.onTouchEvent(event);
    }

    /*
     * 确定点击位置是否在圆内
     * 圆心坐标(x1,y1)  半径 r
     * 点击坐标(x2,y2)
     * */
    private boolean isInCircle(float x1, float y1, float r, float x2, float y2) {
        //计算x的平方
        float x = (x2 - x1) * (x2 - x1);
        //计算y的平方
        float y = (y2 - y1) * (y2 - y1);
        //勾股定理 x方+y方==r方
        if (r * r > x + y) {
            return true;
        } else {
            return false;
        }
    }

    //-----------------------对外提供的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 int getClickResult(){
        return clickResult;
    }

    //开始分裂
    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();
    }
}

 自定义属性

    <declare-styleable name="RippleView3">
        <!--中心圆半径与涟漪初始半径-->
        <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.RippleView3
        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();
        //查询clickResult  一般在新增的点击事件中进行判断
        int i = rippleview.getClickResult();
        //...还有很多提供的GetSet方法 各位自己尝试

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

END

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值