android自定义控件(4)-自定义水波纹效果
一、实现单击出现水波纹单圈效果:
照例来说,还是一个自定义控件,观察这个效果,发现应该需要重写onTouchEvent和onDraw方法,通过在onTouchEvent中获取触摸的坐标,然后以这个坐标值为圆心来绘制我们需要的图形,这个绘制过程就是调用的onDraw方法。
1、新建一个工程,定义一个WaterWave的类,继承自View,作为一个自定义控件;在清单文件中将这个自定义控件写出来,直接填满父窗体。
2、在WaterWave类中,实现它的两参构造函数:
package com.example.waterwavedemo.ui; import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.os.Handler; import android.os.Message; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.View; public class WaterWave extends View { ... /* * 1、两参构造函数 */ public WaterWave(Context context, AttributeSet attrs) { super(context, attrs); alpha = 0; radius = 0; initPaint(); } ... }
3、要使用自定义控件,那么一般都需要指定它的大小,这里我们由于只需要其填满窗体,所以使用默认的onMeasure方法即可:
/** * onMeasure方法,确定控件大小,这里使用默认的 */ @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { // TODO Auto-generated method stub super.onMeasure(widthMeasureSpec, heightMeasureSpec); }
4、将这个自定义图形画出来,重写onDraw方法,在这里由于我们需要画一个圈,所以这样写:
@Override /** * 画出需要的图形的方法,这个方法比较关键 */ protected void onDraw(Canvas canvas) { canvas.drawCircle(xDown, yDown, radius, paint); }
其中的参数xDown和yDown是成员变量,代表按下时的x和y坐标,这个坐标所对应的点就是要绘制的圆环的圆心;radius参数也是成员变量,代表要绘制的圆环的半径;
看到这里还需要一个paint,是Paint类型的画笔对象,这里先将其定义成一个成员变量,由于onDraw方法在第一次自定义控件显示的时候就会被调用,所以这个paint需要我们在两参的构造函数中就进行初始化,否则会报出空指针异常;那么我们这里另外写一个initPaint()方法来初始化我们的paint:
/** * 初始化paint */ private void initPaint() { /* * 新建一个画笔 */ paint = new Paint(); paint.setAntiAlias(true); paint.setStrokeWidth(width); // 设置是环形方式绘制 paint.setStyle(Paint.Style.STROKE); System.out.println("alpha=" + alpha); paint.setAlpha(alpha); System.out.println("得到的透明度:" + paint.getAlpha()); paint.setColor(Color.RED); }
5、触摸定时刷新
在onDraw方法之后,我们已经可以画出这个圆环了,但是实际问题是,我们想要实现点击的时候才在点击的位置来画一个圆环,那么我们肯定需要获得点击的时候的坐标xDown和yDown,所以肯定需要重写onTouchEvent方法,另外我们需要在按下的时候,让透明度是最不透明(alpha=255),在绘制的过程中,让圆环的半径(radius)不断扩大,同时让透明度不断减小,直至完全透明(alpha=0),这个不断变化的过程又需要每隔一段时间重新刷新状态和重新绘制图形,所以我们这里使用handler来处理:
@Override /** * 触摸事件的方法 */ public boolean onTouchEvent(MotionEvent event) { super.onTouchEvent(event); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: radius = 0; alpha = MAX_ALPHA; width = radius / 4; xDown = (int) event.getX(); yDown = (int) event.getY(); handler.sendEmptyMessage(0); break; case MotionEvent.ACTION_MOVE: break; case MotionEvent.ACTION_UP: break; default: break; } return true; }
可以看到,我们这里先只实现了ACTION_DOWN里面的逻辑,在每一个按下的时候将半径radius设置为0,透明度alpha设置为完全不透明,而宽度也为0,并且获取按下的x和y坐标,之后就使用handler发送了一个空消息,让handler去实现定时刷新状态和绘制图形的工作,我们想让圆环的透明度alpha捡到0的时候就不再继续定时自动刷新了,否则在每一次handleMessage的时候都先刷新状态值,然后绘制图形:
private Handler handler = new Handler() { @Override public void handleMessage(Message msg) { super.handleMessage(msg); switch (msg.what) { case 0: flushState(); invalidate(); if (alpha != 0) { // 如果透明度没有到0,则继续刷新,否則停止刷新 handler.sendEmptyMessageDelayed(0, 50); } break; default: break; } } /** * 刷新状态 */ private void flushState() { radius += 5; alpha -= 10; if (alpha < 0) { alpha = 0; } // System.out.println("alpha=" + alpha); width = radius / 4; paint.setAlpha(alpha); paint.setStrokeWidth(width); } };
我们可以看到,在handler中,我们重写了handleMessage方法,在msg.what=0的时候,我们调用flushState()方法来刷新状态,和invalidate()方法来绘制图形,,然后使用handler.sendEmptyMessageDelayed(0, 50);来每隔50毫秒重复一次上面的工作;其中invalidate()是Android提供的,而flushState()则需要我们自己来实现;
按照我们的需求,每一次状态的刷新工作flushState(),我们需要做如下几件事:
(1)让半径增加
(2)让透明度减少,并设置给paint;
(3)环形的宽度增加,并设置给paint
(4)对于透明度而言,最大值是255,但是这里如果让透明度减少到0以下,比如说-1,那么实际上alpha的值不会是-1,而是255+(-1)=254,所以我们还需要加一个判断条件,防止alpha<0
/** * 刷新状态 */ private void flushState() { radius += 5; alpha -= 10; if (alpha < 0) { alpha = 0; } // System.out.println("alpha=" + alpha); width = radius / 4; paint.setAlpha(alpha); paint.setStrokeWidth(width); }
6、在两参的构造函数中添加一些初始化工作:
public WaterWave(Context context, AttributeSet attrs) { super(context, attrs); alpha = 0; radius = 0; initPaint(); }
至此,我们的第一步就基本完成了
二、实现多次点击圆环同时存在,同时刷新效果:
从面图二中,我们不难发现,不论如何点击,屏幕上都只会同时存在一个圆圈的效果,这是因为我们每次点击的时候,都重新设置了圆心,而且所有圆形的参数都是成员变量,都是共享的;不仅如此,如果在上一个圆圈没有消失的时候,就再次点击,会让新出现的圆圈变大的速度大大增加,这是因为使用handler.sendEmptyMessageDelayed(0,50)方法的原因,第二次点击时会重复触发这个方法,使得前后两次点击的handler.sendEmptyMessageDelayed()重叠生效,让实际间隔远远小于50毫秒,所以刷新速度快了很多
那么我们现在就要解决上面两个小问题,实现如下图的效果:
解决这两个小问题的思路:
1、针对所有水波纹圆圈共享参数的问题:
方法就是新建一个内部类Wave,用于存放每个圆圈的参数,每一个圆圈都对应一个Wave对象,然后在onDraw方法里面,同时重绘所有的圆圈视图;那么这里就还需要一个List集合waveList,用于存放所有的wave对象,方便遍历。
2、针对handler.sendEmptyMessageDelayed方法在后续点击的时候不断被调用,导致刷新越来越快的问题。
这里可以设置一个成员变量 boolean isStart;来标志是不是第一次按下;因为我们在第一次按下的时候,肯定是希望开始定时刷新,调用handler.sendEmptyMessageDelayed,让圆环的状态不断变化。但是对于之后的点击,我们其实只希望它立刻被刷新一次,并被加入到waveList集合中,而并不需要发送一个handler的信息来调用handler.sendEmptyMessageDelayed。所以在一开始的时候我们将其设置为true,而在第一次点击时候将其设置为false,那么在什么时候将其设置为false呢,这里牵涉到第三个问题:
3、对于waveList集合而言,如果一直点击往集合里面添加Wave对象,那么无疑会让这个集合越来越大,这个是我们不希望看到的。
我们希望在圆环的透明度值alpha变为0,也就是完全透明的时候,让其从waveList中remove掉,让其能被垃圾回收回收掉,这样如果点击几个点之后停止,点都会自动消失(alpha值减到0),那么对应的Wave对象也会从waveList被移除,waveList的大小也会变成0,这个时候我们就可以停止handler.sendEmptyMessageDelayed方法继续被调用,同时可以将isStart重新设为true。那么isStart何时设为false呢?我们可以在flushState刷新状态的时候将其设为false,因为刷新状态的时候表明第一次点击已经按下了。然后在onTouchEvent方法的ACTION_DWON条件下,如果isStart为true才发送handler的消息,这代表第一次点击,之后再点击也不会发送而只是将wave对象添加到waveList中,因为第一次的时候调用flushState已经将isStart置为false了。
由于改动较大,代码如下:
package com.example.waterwavedemo.ui; import java.util.ArrayList; import java.util.Collections; import java.util.List; import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.os.Handler; import android.os.Message; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.View; public class WaterWave extends View { /** * 波形的List */ private List<Wave> waveList; /** * 最大的不透明度,完全不透明 */ private static final int MAX_ALPHA = 255; protected static final int FLUSH_ALL = -1; private boolean isStart = true; // /** // * 按下的时候x坐标 // */ // private int xDown; // /** // * 按下的时候y的坐标 // */ // private int yDown; // /** // * 用来表示圆环的半径 // */ // private float radius; // private int alpha; /* * 1、两参构造函数 */ public WaterWave(Context context, AttributeSet attrs) { super(context, attrs); waveList = Collections.synchronizedList(new ArrayList<Wave>()); } /** * onMeasure方法,确定控件大小,这里使用默认的 */ @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); } @Override /** * 画出需要的图形的方法,这个方法比较关键 */ protected void onDraw(Canvas canvas) { // 重绘所有圆环 for (int i = 0; i < waveList.size(); i++) { Wave wave = waveList.get(i); canvas.drawCircle(wave.xDown, wave.yDown, wave.radius, wave.paint); } } /** * 初始化paint */ private Paint initPaint(int alpha, float width) { /* * 新建一个画笔 */ Paint paint = new Paint(); paint.setAntiAlias(true); paint.setStrokeWidth(width); // 设置是环形方式绘制 paint.setStyle(Paint.Style.STROKE); // System.out.println("alpha=" + alpha); paint.setAlpha(alpha); // System.out.println("得到的透明度:" + paint.getAlpha()); paint.setColor(Color.RED); return paint; } private Handler handler = new Handler() { @Override public void handleMessage(Message msg) { super.handleMessage(msg); switch (msg.what) { case 0: flushState(); invalidate(); if (waveList != null && waveList.size() > 0) { handler.sendEmptyMessageDelayed(0, 50); } break; default: break; } } }; /** * 刷新状态 */ private void flushState() { for (int i = 0; i < waveList.size(); i++) { Wave wave = waveList.get(i); if (isStart == false && wave.alpha == 0) { waveList.remove(i); wave.paint = null; wave = null; continue; } else if (isStart == true) { isStart = false; } wave.radius += 5; wave.alpha -= 10; if (wave.alpha < 0) { wave.alpha = 0; } wave.width = wave.radius / 4; wave.paint.setAlpha(wave.alpha); wave.paint.setStrokeWidth(wave.width); } } // private Paint paint; // private float width; @Override /** * 触摸事件的方法 */ public boolean onTouchEvent(MotionEvent event) { super.onTouchEvent(event); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: Wave wave = new Wave(); wave.radius = 0; wave.alpha = MAX_ALPHA; wave.width = wave.radius / 4; wave.xDown = (int) event.getX(); wave.yDown = (int) event.getY(); wave.paint = initPaint(wave.alpha, wave.width); if (waveList.size() == 0) { isStart = true; } System.out.println("isStart=" + isStart); waveList.add(wave); // 点击之后刷洗一次图形 invalidate(); if (isStart) { handler.sendEmptyMessage(0); } break; case MotionEvent.ACTION_MOVE: break; case MotionEvent.ACTION_UP: break; default: break; } return true; } private class Wave { int waveX; int waveY; /** * 用来表示圆环的半径 */ float radius; Paint paint; /** * 按下的时候x坐标 */ int xDown; /** * 按下的时候y的坐标 */ int yDown; float width; int alpha; } }
三、实现完全效果(点击和移动,颜色随机,圆圈大小变化速度)
效果图就是跟图一的一样了,主要做几个小地方:
1、让onTouchEvent里面的ACTION_DOWN和ACTION_MOVE响应同样的事件,实际上就是去掉ACTION_DOWN的break;然后将处理代码写到随后的ACTION_MOVE中去即可
2、新建一个成员变量数组colors,里面放自己想要的颜色,然后在initPaint方法的设置color的时候,使用paint.setColor(colors[(int) (Math.random() * (colors.length - 1))]);
3、控制波形的变化趋势,这个看个人爱好,我是这样做的:在flushState中:
wave.radius += waveList.size() - i; wave.width = (wave.radius / 3); wave.paint.setStrokeWidth(wave.width); // wave.alpha -= 10; if (wave.alpha < 0) { wave.alpha = 0; } // wave.width = wave.radius / 4; wave.paint.setAlpha(wave.alpha);
至此,就完成了自定义的水波纹效果了。存在的问题就是,如果在模拟器上,快速滑动,会有卡顿,在我的手机Nexus5上,还算流畅,应该跟内存无关,后续可能还会做一些优化。
android自定义控件(4)-自定义水波纹效果的更多相关文章
- Android点击Button水波纹效果
先上图,看看接下来我要向大家介绍的是个什么东西,例如以下图: 接下来要介绍的就是怎样实现上述图中的波纹效果.这样的效果假设大家没有体验过的话,能够看看百度手机卫士或者360手机卫士,里面的按钮点击效果 ...
- 关于自定义view--实现自定义水波纹效果
开发中的东西太多,怕自己忘记了,简单记录一下. 声明:此控件借鉴了大佬的想法,在此感谢大佬提供的支持,我只是把大佬的想法拿出来而已. ok,废话到此结束,看效果: 分析一下,我们可以看到,图中有两个圆 ...
- Android自定义控件-Path之贝赛尔曲线和手势轨迹、水波纹效果
从这篇开始,我将延续androidGraphics系列文章把图片相关的知识给大家讲完,这一篇先稍微进阶一下,给大家把<android Graphics(二):路径及文字>略去的quadTo ...
- Android 自定义view实现水波纹效果
http://blog.csdn.net/tianjian4592/article/details/44222565 在实际的开发中,很多时候还会遇到相对比较复杂的需求,比如产品妹纸或UI妹纸在哪看了 ...
- 自定义view实现水波纹效果
水波纹效果: 1.标准正余弦水波纹: 2.非标准圆形液柱水波纹: 虽说都是水波纹,但两者在实现上差异是比较大的,一个通过正余弦函数模拟水波纹效果,另外一个会运用到图像的混合模式(PorterDuffX ...
- 兼容Android的水波纹效果
Android的水波纹效果只有高版本才有,我们希望自己的应用在低版本用低版本的阴影,高版本用水波纹,这怎么做呢?其实,只要分drawable和drawablev21两个文件夹就好了. 普通情况下的se ...
- Android 颜色渲染(七) RadialGradient 环形渲染实现水波纹效果
利用环形渲染我们可以做到什么? 其实很多都是非常常见的,比如上一篇实现的帮帮糖效果, 彩色的热气球,比如这里要讲到的水波纹效果,或者也可以理解为扩散色渲染效果 首先看一下效果图: 轻触屏幕,即可看到对 ...
- android 点击水波纹效果
这里是重点,<ripple>是API21才有的新Tag,正是实现水波纹效果的; 其中<ripple android:color="#FF21272B" .... ...
- Android自己定义控件系列五:自己定义绚丽水波纹效果
尊重原创!转载请注明出处:http://blog.csdn.net/cyp331203/article/details/41114551 今天我们来利用Android自己定义控件实现一个比較有趣的效果 ...
随机推荐
- 匿名内部类为什么访问外部类局部变量必须是final的?
1.内部类里面使用外部类的局部变量时,其实就是内部类的对象在使用它,内部类对象生命周期中都可能调用它,而内部类试图访问外部方法中的局部变量时,外部方法的局部变量很可能已经不存在了,那么就得延续其生命, ...
- 60.Android通用流行框架大全
转载:https://segmentfault.com/a/1190000005073746 Android通用流行框架大全 1. 缓存 名称 描述 DiskLruCache Java实现基于LRU的 ...
- 【BZOJ-1568】Blue Mary开公司 李超线段树 (标记永久化)
1568: [JSOI2008]Blue Mary开公司 Time Limit: 15 Sec Memory Limit: 162 MBSubmit: 557 Solved: 192[Submit ...
- Android成长日记-五大布局
1. 五布局之线性布局LinearLayout 特点:它包含的子控件将以横向或竖向的方式排列 ps:android:gravity=”center|bottom”(gravity允许多级联用) Tip ...
- C# winform窗体设计-通过条件查询数据
在winform 数据库设计中,有时候需要通过条件查询对应的数据,并且将数据显示在文本框(or 富文本框)中,下面,小编将讲述通过一个条件: 首先,我们需要对数据库建立连接,并且执行数据库命令,在此之 ...
- Linux Network Related Drive
catalog . 通过套接字通信 . 网络实现的分层模型 . 网络命名空间 . 套接字缓冲区 . 网络访问层 . 网络层 . 传输层 . 应用层 . 内核内部的网络通信 1. 通过套接字通信 Lin ...
- 第一个有点作用的PHP扩展
C/C++去开发PHP扩展 我觉的对于PHP开发人员来说,学的东西非常杂,也非常多,当然了开发PHP扩展也是一个必须要掌握的技能,这里膜拜下大神鸟哥(Laruence)~ 今天要开发的第一个有点功能的 ...
- Error=Bias+Variance
首先 Error = Bias + Variance Error反映的是整个模型的准确度,Bias反映的是模型在样本上的输出与真实值之间的误差,即模型本身的精准度,Variance反映的是模型每一次输 ...
- 初学angular-简单的angular指令
实现一个简单的input清空内容,且清空对应ngModel 前台部分 <html ng-app="hpapp"> <head> <meta chars ...
- 利用mybatis的分页插件实现商品列表的显示
分析思路: 当我们点击查询商品的时候,会出现商品的列表,并按上下页可以实现分页的查询的功能. 首先首先我们先找到商品查询商品的按钮在jsp的那个页面,即首页index.jsp 这里有个url即显示商品 ...