RippleView3效果图

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

说明

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

RippleView系列- ->RippleView、 RippleView2、RippleView3

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;}/** 测量* */@Overrideprotected 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;}/** 绘制* */@Overrideprotected 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/drawCounthandler.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);}//如果分裂对象半径小于minRif (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 / 20split.r += minR / 20;}}//内部Handler 实现涟漪周期荡漾private Handler handler = new Handler() {@Overridepublic 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;}}};@Overridepublic 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();}//刷新Viewprivate void reDraw() {//清除handler的回调与Message 避免同时传递多个Message导致速率异常handler.removeCallbacksAndMessages(null);//同时重绘Viewinvalidate();}
}

自定义属性

    <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.RippleView3android: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

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

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

    RippleView2效果图 新增了分裂与复原效果 暂时只支持分裂234个分裂对象 说明 在之前的RippleView的基础上 增加了分裂效果 我称之为RippleView2 RippleView系列 ...

  2. Android自定义view原理及自定义View示例

    自定义view如何分类 自定义View:只需要重写onMeasure()和onDraw(),在没有现成的View,需要自己实现的时候,就使用自定义View,一般继承自View,SurfaceView或 ...

  3. [安卓开发]弹幕滚幕效果自定义View之BarrageView|支持点击事件|隐藏不滞留|颜色随机|大小速度范围随机

    安卓弹幕滚幕效果自定义View之BarrageView|支持点击事件|隐藏不滞留|颜色随机|大小速度范围随机 1.简介 项目地址: https://github.com/tpnet/BarrageVi ...

  4. Android冒险之旅-22-自定义View--涟漪效果|波浪效果

    RippleView效果图 希望是你们心心念念的有涟漪.波浪效果的自定义View 说明 RippleView系列- ->RippleView. RippleView2.RippleView3 R ...

  5. 精通Android自定义View(十九)自定义圆形炫彩加载转圈效果

    1 效果 2 源码 public class JiondongView extends View {private Paint mBackgroundPaint;private float mScal ...

  6. android自定义view设置高度,自定义View的宽高设定

    关于View的属性 自定义动态设置View的大小属性 使用LayoutParams来设置view的宽高. int textLen = AddShopActivity.mCategoryItemName ...

  7. android 画圆弧动画,『Android自定义View实战』自定义带入场动画的弧形百分比进度条...

    写在前面 这是在简书发表的处女座,这个想法也停留在脑海中很久了,一直拖到现在(懒癌发作2333),先自我介绍一番,一枚刚毕业不久的Android程序猿,初出茅庐的Android小生,之前一直在CSDN ...

  8. android仿微博头像_Android 自定义 View 集锦|自定义圆形旋转进度条,仿微博头像加载效果...

    微博 App 的用户头像有一个圆形旋转进度条的加载效果,看上去效果非常不错,如图所示: 据说 Instagram 也采用了这种效果.最近抽空研究了一下,最后实现的效果是这样: 基本上能模拟出个大概,代 ...

  9. Android自定义View如此简单 实现点击动画+进度刷新的提交/下载按钮(填坑面试题)

    SubmitButton 背景 实现思路 继承View 面试题:构造方法如何选择 自定义属性 面试题:styleable.AttributeSet.TypedArray的关系 测量宽高 面试题:UNS ...

最新文章

  1. C++中std::sort/std::stable_sort/std::partial_sort的区别及使用
  2. 织梦DeDE调用文章第一张图片的方法
  3. 智慧医疗解决医生“带病工作、超负荷工作”
  4. dbeaver无法修改表数据_为什么你用不好数据透视表,看完才知道
  5. mysql登录不了了_登录不了MySQL的解决方法
  6. ASP.NET 连接MySql数据库
  7. RS485数据光端机产品特点及技术参数介绍
  8. Java CharArrayWriter size()方法与示例
  9. ERROR: Minions returned with non-zero exit code
  10. Auto 和 Decltye 的区别
  11. 计算机前置usb应用,电脑前置usb和后置usb的区别
  12. Inception-v4论文总结
  13. layuit 框架_Layui|经典模块化前端框架
  14. Serverless Framework 无服务器架构
  15. 内存池算法 linux,内存池自动释放 - 梦想游戏 - OSCHINA - 中文开源技术交流社区...
  16. idea启动SpringBoot项目自动停止
  17. java源代码反编译_XJad(Java源程序反编译软件)
  18. sigmoid代码实现
  19. 微信小程序:身份证号码+手机号校验
  20. Java线程状态总结

热门文章

  1. js 判断数组中对象是否存在某个值,应用到some()方法
  2. 祝贺 | 蚂蚁金服年轻科学家曾晓东入选MIT TR35中国榜单
  3. ngram java_突出与模糊和ngram
  4. 计量经济学笔记之OLS回归的推导
  5. java成为移动互联网时代必学语言的六大理由
  6. 利用python计算学生成绩
  7. jQuery日历插件酒店预订蓝色
  8. 数据分析,这样满足运营的需求(实操版)
  9. CentOS7的安装流程
  10. 10个你需要知道的开源服务器技术