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