先来看一下这次要实现的最终效果:
首先来实现效果一,为实现效果二做充足的准备,下面开始:
新建工程,并定义一个自定义View,然后将其定义在布局文件中,里面是空实现,之后会一步步来填充代码:
MyRing.java:
public class MyRing extends View { public MyRing(Context context, AttributeSet attrs) { super(context, attrs); } }
activity_main.xml:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity" > <com.example.waveview.MyRing android:layout_width="match_parent" android:layout_height="match_parent" /> </RelativeLayout>
接下来一步步来实现第一张效果图所示的效果,先重写父类的某些方法:
public class MyRing extends View { public MyRing(Context context, AttributeSet attrs) { super(context, attrs); } /** * 大小的测量按系统的默认规则 */ @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); } /** * 绘制我们的内容 */ @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); } }
在点击时需要画一个圆,这是第一步,所以需要监听触摸事件:
public class MyRing extends View { /** * 圆环圆心的X坐标 */ private int cx; /** * 圆环圆心的Y坐标 */ private int cy; private Paint paint; /** * 线条的厚度 */ private float strokeWidth; public MyRing(Context context, AttributeSet attrs) { super(context, attrs); // 初始化paint paint = new Paint(); paint.setAntiAlias(true); paint.setColor(Color.RED); paint.setStyle(Style.STROKE); // 刻画,画线条 paint.setStrokeWidth(strokeWidth); // 设置条线的厚度 } /** * 大小的测量按系统的默认规则 */ @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); } /** * 绘制我们的内容 */ @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); /** * 绘制圆环,为了看到效果先将半径写死 */ canvas.drawCircle(cx, cy, 100, paint); } @Override public boolean onTouchEvent(MotionEvent event) { super.onTouchEvent(event); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: // 点击,获得圆环的中心 cx = (int) event.getX(); cy = (int) event.getY(); invalidate(); break; } return true; } }
效果如下:
默认圆画在(0,0)的位置,有了第一步之后,接下来则要让这个圆进行动态渐变,半径、圆边线的粗度都得动态去改变,具体代码如下:
public class MyRing extends View { /** * 圆环圆心的X坐标 */ private int cx; /** * 圆环圆心的Y坐标 */ private int cy; private Paint paint; /** * 线条的厚度 */ private float strokeWidth; /** * 圆环的半径 */ private float radius; private Handler handler = new Handler() { public void handleMessage(android.os.Message msg) { flushState(); // 刷新页面 执行onDraw()方法 invalidate(); if (paint.getAlpha() != 0) { handler.sendEmptyMessageDelayed(0, 100); } }; }; public MyRing(Context context, AttributeSet attrs) { super(context, attrs); initView(); } private void initView() { // 初始化paint paint = new Paint(); paint.setAntiAlias(true); // 抗矩齿 paint.setColor(Color.RED); paint.setStyle(Style.STROKE); // 刻画,画线条 paint.setStrokeWidth(strokeWidth); // 设置条线的厚度 paint.setAlpha(255); // 设置透明度 ,0--255 0代表完全透明 // this.radius = 0; strokeWidth = 0; } /** * 大小的测量按系统的默认规则 */ @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); } /** * 绘制我们的内容 */ @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); /** * 绘制圆环 */ canvas.drawCircle(cx, cy, radius, paint); } @Override public boolean onTouchEvent(MotionEvent event) { super.onTouchEvent(event); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: // 点击,获得圆环的中心 cx = (int) event.getX(); cy = (int) event.getY(); // 初始化画笔 initView(); handler.sendEmptyMessage(0); break; } return true; } /* * 刷新状态 */ private void flushState() { this.radius += 10; this.strokeWidth = radius / 4; paint.setStrokeWidth(strokeWidth); int nextAlpha = paint.getAlpha() - 20; if (nextAlpha <= 20) { nextAlpha = 0; } paint.setAlpha(nextAlpha); } }
这时就是图一的效果了,有了这个基础,要实现图二的效果也就不难了,下面来试一下:
由于是多个圆,所以肯定是需要一个集合来存储,另外需要用一个实体来表示一个圆的多个属性,如下:
/** * 定义一个波浪 * @author leo */ private class Wave { //圆心 int cx; int cy; //画笔 Paint p; //半径 int r; }
然后在按下或滑动时,则把相应的点给添加到集合中,并且需要点与点之间有一定的距离才行,挨得太近则不添加,具体逻辑如下:
@Override public boolean onTouchEvent(MotionEvent event) { super.onTouchEvent(event); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: case MotionEvent.ACTION_MOVE: int x = (int) event.getX(); int y = (int) event.getY(); addPoint(x,y); break; default: break; } return true; }
而添加的方法如下:
/** * 添加新的波浪中心点 * @param x * @param y */ private void addPoint(int x, int y) { if(wList.size() == 0){ addPoint2List(x,y); /* * 第一次启动动画 */ isRunning = true; handler.sendEmptyMessage(0); }else{ Wave w = wList.get(wList.size()-1); if(Math.abs(w.cx - x)>DIS_SOLP || Math.abs(w.cy-y)>DIS_SOLP){ addPoint2List(x,y); } }; } /** * 添加新的波浪 * @param x * @param y */ private void addPoint2List(int x, int y) { Wave w = new Wave(); w.cx = x; w.cy=y; Paint pa=new Paint(); pa.setColor(colors[(int)(Math.random()*4)]); pa.setAntiAlias(true); pa.setStyle(Style.STROKE); w.p = pa; wList.add(w); }
其中当一第一次添加时,则会开启handler去跑圆环动画,其它的则相继往集合中添加,而handler的写法跟一个圆环的写法差不多,这里就不多解释,直接把整个代码贴上来,都比较好理解:
/** * 水波纹效果 * */ public class MyRingWave extends View{ /** * 二个相临波浪中心点的最小距离 */ private static final int DIS_SOLP = 13; protected boolean isRunning = false; private ArrayList<Wave> wList; public MyRingWave(Context context, AttributeSet attrs) { super(context, attrs); wList = new ArrayList<MyRingWave.Wave>(); } private Handler handler = new Handler(){ public void handleMessage(android.os.Message msg) { //刷新数据 flushData(); //刷新页面 invalidate(); //循环动画 if (isRunning) { handler.sendEmptyMessageDelayed(0, 50); } }; }; @Override protected void onDraw(Canvas canvas) { for (int i = 0; i < wList.size(); i++) { Wave wave = wList.get(i); canvas.drawCircle(wave.cx, wave.cy, wave.r, wave.p); } } @Override public boolean onTouchEvent(MotionEvent event) { super.onTouchEvent(event); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: case MotionEvent.ACTION_MOVE: int x = (int) event.getX(); int y = (int) event.getY(); addPoint(x,y); break; default: break; } return true; } /** * 添加新的波浪中心点 * @param x * @param y */ private void addPoint(int x, int y) { if(wList.size() == 0){ addPoint2List(x,y); /* * 第一次启动动画 */ isRunning = true; handler.sendEmptyMessage(0); }else{ Wave w = wList.get(wList.size()-1); if(Math.abs(w.cx - x)>DIS_SOLP || Math.abs(w.cy-y)>DIS_SOLP){ addPoint2List(x,y); } }; } /** * 添加新的波浪 * @param x * @param y */ private void addPoint2List(int x, int y) { Wave w = new Wave(); w.cx = x; w.cy=y; Paint pa=new Paint(); pa.setColor(colors[(int)(Math.random()*4)]); pa.setAntiAlias(true); pa.setStyle(Style.STROKE); w.p = pa; wList.add(w); } private int [] colors = new int[]{Color.BLUE,Color.RED,Color.YELLOW,Color.GREEN}; /** * 刷新数据 */ private void flushData() { for (int i = 0; i < wList.size(); i++) { Wave w = wList.get(i); //如果透明度为 0 从集合中删除 int alpha = w.p.getAlpha(); if(alpha == 0){ wList.remove(i); //删除i 以后,i的值应该再减1 否则会漏掉一个对象,不过,在此处影响不大,效果上看不出来。 continue; } alpha-=5; if(alpha<5){ alpha =0; } //降低透明度 w.p.setAlpha(alpha); //扩大半径 w.r = w.r+3; //设置半径厚度 w.p.setStrokeWidth(w.r/3); } /* * 如果集合被清空,就停止刷新动画 */ if(wList.size() == 0){ isRunning = false; } } /** * 定义一个波浪 * @author leo */ private class Wave { //圆心 int cx; int cy; //画笔 Paint p; //半径 int r; } }
对于这个自定义效果倒不是很复杂,但效果还是挺炫滴,以后坚持收集学习好的UI效果。