├── app
├── .gitignore
├── src
│ └── main
│ │ ├── res
│ │ ├── values
│ │ │ ├── strings.xml
│ │ │ ├── colors.xml
│ │ │ ├── dimens.xml
│ │ │ ├── styles.xml
│ │ │ └── attrs.xml
│ │ ├── mipmap-hdpi
│ │ │ └── ic_launcher.png
│ │ ├── mipmap-mdpi
│ │ │ └── ic_launcher.png
│ │ ├── mipmap-xhdpi
│ │ │ └── ic_launcher.png
│ │ ├── mipmap-xxhdpi
│ │ │ └── ic_launcher.png
│ │ ├── mipmap-xxxhdpi
│ │ │ └── ic_launcher.png
│ │ ├── values-w820dp
│ │ │ └── dimens.xml
│ │ └── layout
│ │ │ └── activity_main.xml
│ │ ├── java
│ │ └── zhuwentao
│ │ │ └── com
│ │ │ └── rippleview
│ │ │ ├── MainActivity.java
│ │ │ ├── DensityUtil.java
│ │ │ └── RippleView.java
│ │ └── AndroidManifest.xml
├── proguard-rules.pro
└── build.gradle
├── settings.gradle
├── README.md
└── gradle.properties
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app'
2 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | zwt_RippleView
3 |
4 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zhuwentao2150/RippleView/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zhuwentao2150/RippleView/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zhuwentao2150/RippleView/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zhuwentao2150/RippleView/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zhuwentao2150/RippleView/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #3F51B5
4 | #303F9F
5 | #FF4081
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 16dp
4 | 16dp
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/values-w820dp/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 64dp
6 |
7 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # RippleView
2 |
3 | Android 自定义View一个控件搞定多种水波纹涟漪扩散效果
4 |
5 | > 博客地址:http://blog.csdn.net/zhuwentao2150/article/details/79452735
6 |
7 | - 圆圈从中心可循环向外扩散
8 | - 圆圈之间的扩散间距可以改变
9 | - 可控制扩散圆的渐变度
10 | - 圆圈可以是线条样式或者实心样式
11 | - 圆圈扩散的速度可以控制
12 | - 适配圆圈不同大小下的扩散效果
13 |
14 | 
15 |
--------------------------------------------------------------------------------
/app/src/main/java/zhuwentao/com/rippleview/MainActivity.java:
--------------------------------------------------------------------------------
1 | package zhuwentao.com.rippleview;
2 |
3 | import android.support.v7.app.AppCompatActivity;
4 | import android.os.Bundle;
5 |
6 | public class MainActivity extends AppCompatActivity {
7 |
8 | @Override
9 | protected void onCreate(Bundle savedInstanceState) {
10 | super.onCreate(savedInstanceState);
11 | setContentView(R.layout.activity_main);
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/app/src/main/res/values/attrs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in D:\Users\Administrator\AppData\Local\Android\sdk/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 |
3 | # IDE (e.g. Android Studio) users:
4 | # Gradle settings configured through the IDE *will override*
5 | # any settings specified in this file.
6 |
7 | # For more details on how to configure your build environment visit
8 | # http://www.gradle.org/docs/current/userguide/build_environment.html
9 |
10 | # Specifies the JVM arguments used for the daemon process.
11 | # The setting is particularly useful for tweaking memory settings.
12 | org.gradle.jvmargs=-Xmx1536m
13 |
14 | # When configured, Gradle will run in incubating parallel mode.
15 | # This option should only be used with decoupled projects. More details, visit
16 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
17 | # org.gradle.parallel=true
18 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | android {
4 | compileSdkVersion 23
5 | buildToolsVersion "23.0.3"
6 | defaultConfig {
7 | applicationId "zhuwentao.com.rippleview"
8 | minSdkVersion 14
9 | targetSdkVersion 23
10 | versionCode 1
11 | versionName "1.0"
12 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
13 | }
14 | buildTypes {
15 | release {
16 | minifyEnabled false
17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
18 | }
19 | }
20 | }
21 |
22 | dependencies {
23 | compile fileTree(dir: 'libs', include: ['*.jar'])
24 | androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
25 | exclude group: 'com.android.support', module: 'support-annotations'
26 | })
27 | compile 'com.android.support:appcompat-v7:23.4.0'
28 | testCompile 'junit:junit:4.12'
29 | }
30 |
--------------------------------------------------------------------------------
/app/src/main/java/zhuwentao/com/rippleview/DensityUtil.java:
--------------------------------------------------------------------------------
1 |
2 | package zhuwentao.com.rippleview;
3 |
4 | import android.content.Context;
5 |
6 | /**
7 | * dp与px相互转换的工具类
8 | */
9 | public class DensityUtil {
10 |
11 | /**
12 | * 根据手机的分辨率从 dp 的单位 转成为 px(像素)
13 | * @param context 上下文
14 | * @param dpValue dp值
15 | * @return px值
16 | */
17 | public static int dip2px(Context context, float dpValue) {
18 | final float scale = context.getResources().getDisplayMetrics().density;
19 | return (int) (dpValue * scale + 0.5f);
20 | }
21 |
22 | /**
23 | * 根据手机的分辨率从 px(像素) 的单位 转成为 dp
24 | * @param context 上下文
25 | * @param pxValue px值
26 | * @return dp值
27 | */
28 | public static int px2dip(Context context, float pxValue) {
29 | final float scale = context.getResources().getDisplayMetrics().density;
30 | return (int) (pxValue / scale + 0.5f);
31 | }
32 |
33 | /**
34 | * 将px值转换为sp值,保证文字大小不变
35 | *
36 | * @param context 上下文对象
37 | * @param pxValue 字体大小像素
38 | * @return 字体大小sp
39 | */
40 | public static int px2sp(Context context, float pxValue) {
41 | final float fontScale = context.getResources().getDisplayMetrics().scaledDensity;
42 | return (int) (pxValue / fontScale + 0.5f);
43 | }
44 |
45 | /**
46 | * 将sp值转换为px值,保证文字大小不变
47 | *
48 | * @param context 上下文对象
49 | * @param spValue 字体大小sp
50 | * @return 字体大小像素
51 | */
52 | public static int sp2px(Context context, float spValue) {
53 | final float fontScale = context.getResources().getDisplayMetrics().scaledDensity;
54 | return (int) (spValue * fontScale + 0.5f);
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
11 |
16 |
17 |
24 |
25 |
32 |
33 |
40 |
41 |
42 |
47 |
48 |
55 |
56 |
62 |
63 |
69 |
70 |
71 |
76 |
77 |
83 |
84 |
90 |
91 |
97 |
98 |
99 |
104 |
105 |
112 |
113 |
120 |
121 |
128 |
129 |
130 |
131 |
--------------------------------------------------------------------------------
/app/src/main/java/zhuwentao/com/rippleview/RippleView.java:
--------------------------------------------------------------------------------
1 | package zhuwentao.com.rippleview;
2 |
3 | import android.content.Context;
4 | import android.content.res.TypedArray;
5 | import android.graphics.Canvas;
6 | import android.graphics.Color;
7 | import android.graphics.Paint;
8 | import android.util.AttributeSet;
9 | import android.view.View;
10 |
11 | import java.util.ArrayList;
12 | import java.util.List;
13 |
14 | /**
15 | * 涟漪效果
16 | *
17 | * Created by zhuwentao on 2018-03-07.
18 | */
19 | public class RippleView extends View {
20 |
21 | private Context mContext;
22 |
23 | // 画笔对象
24 | private Paint mPaint;
25 |
26 | // View宽
27 | private float mWidth;
28 |
29 | // View高
30 | private float mHeight;
31 |
32 | // 声波的圆圈集合
33 | private List mRipples;
34 |
35 | private int sqrtNumber;
36 |
37 | // 圆圈扩散的速度
38 | private int mSpeed;
39 |
40 | // 圆圈之间的密度
41 | private int mDensity;
42 |
43 | // 圆圈的颜色
44 | private int mColor;
45 |
46 | // 圆圈是否为填充模式
47 | private boolean mIsFill;
48 |
49 | // 圆圈是否为渐变模式
50 | private boolean mIsAlpha;
51 |
52 | public RippleView(Context context) {
53 | this(context, null);
54 | }
55 |
56 | public RippleView(Context context, AttributeSet attrs) {
57 | this(context, attrs, 0);
58 | }
59 |
60 | public RippleView(Context context, AttributeSet attrs, int defStyleAttr) {
61 | super(context, attrs, defStyleAttr);
62 | // 获取用户配置属性
63 | TypedArray tya = context.obtainStyledAttributes(attrs, R.styleable.mRippleView);
64 | mColor = tya.getColor(R.styleable.mRippleView_cColor, Color.BLUE);
65 | mSpeed = tya.getInt(R.styleable.mRippleView_cSpeed, 1);
66 | mDensity = tya.getInt(R.styleable.mRippleView_cDensity, 10);
67 | mIsFill = tya.getBoolean(R.styleable.mRippleView_cIsFill, false);
68 | mIsAlpha = tya.getBoolean(R.styleable.mRippleView_cIsAlpha, false);
69 | tya.recycle();
70 |
71 | init();
72 | }
73 |
74 | private void init() {
75 | mContext = getContext();
76 |
77 | // 设置画笔样式
78 | mPaint = new Paint();
79 | mPaint.setColor(mColor);
80 | mPaint.setStrokeWidth(DensityUtil.dip2px(mContext, 1));
81 | if (mIsFill) {
82 | mPaint.setStyle(Paint.Style.FILL);
83 | } else {
84 | mPaint.setStyle(Paint.Style.STROKE);
85 | }
86 | mPaint.setStrokeCap(Paint.Cap.ROUND);
87 | mPaint.setAntiAlias(true);
88 |
89 | // 添加第一个圆圈
90 | mRipples = new ArrayList<>();
91 | Circle c = new Circle(0, 255);
92 | mRipples.add(c);
93 |
94 | mDensity = DensityUtil.dip2px(mContext, mDensity);
95 |
96 | // 设置View的圆为半透明
97 | setBackgroundColor(Color.TRANSPARENT);
98 | }
99 |
100 | @Override
101 | public void onDraw(Canvas canvas) {
102 | super.onDraw(canvas);
103 |
104 | // 内切正方形
105 | drawInCircle(canvas);
106 |
107 | // 外切正方形
108 | // drawOutCircle(canvas);
109 | }
110 |
111 | /**
112 | * 圆到宽度
113 | *
114 | * @param canvas
115 | */
116 | private void drawInCircle(Canvas canvas) {
117 | canvas.save();
118 |
119 | // 处理每个圆的宽度和透明度
120 | for (int i = 0; i < mRipples.size(); i++) {
121 | Circle c = mRipples.get(i);
122 | mPaint.setAlpha(c.alpha);// (透明)0~255(不透明)
123 | canvas.drawCircle(mWidth / 2, mHeight / 2, c.width - mPaint.getStrokeWidth(), mPaint);
124 |
125 | // 当圆超出View的宽度后删除
126 | if (c.width > mWidth / 2) {
127 | mRipples.remove(i);
128 | } else {
129 | // 计算不透明的数值,这里有个小知识,就是如果不加上double的话,255除以一个任意比它大的数都将是0
130 | if (mIsAlpha) {
131 | double alpha = 255 - c.width * (255 / ((double) mWidth / 2));
132 | c.alpha = (int) alpha;
133 | }
134 | // 修改这个值控制速度
135 | c.width += mSpeed;
136 | }
137 | }
138 |
139 |
140 | // 里面添加圆
141 | if (mRipples.size() > 0) {
142 | // 控制第二个圆出来的间距
143 | if (mRipples.get(mRipples.size() - 1).width > DensityUtil.dip2px(mContext, mDensity)) {
144 | mRipples.add(new Circle(0, 255));
145 | }
146 | }
147 |
148 |
149 | invalidate();
150 |
151 | canvas.restore();
152 | }
153 |
154 |
155 | /**
156 | * 圆到对角线
157 | *
158 | * @param canvas
159 | */
160 | private void drawOutCircle(Canvas canvas) {
161 | canvas.save();
162 |
163 | // 使用勾股定律求得一个外切正方形中心点离角的距离
164 | sqrtNumber = (int) (Math.sqrt(mWidth * mWidth + mHeight * mHeight) / 2);
165 |
166 | // 变大
167 | for (int i = 0; i < mRipples.size(); i++) {
168 |
169 | // 启动圆圈
170 | Circle c = mRipples.get(i);
171 | mPaint.setAlpha(c.alpha);// (透明)0~255(不透明)
172 | canvas.drawCircle(mWidth / 2, mHeight / 2, c.width - mPaint.getStrokeWidth(), mPaint);
173 |
174 | // 当圆超出对角线后删掉
175 | if (c.width > sqrtNumber) {
176 | mRipples.remove(i);
177 | } else {
178 | // 计算不透明的度数
179 | double degree = 255 - c.width * (255 / (double) sqrtNumber);
180 | c.alpha = (int) degree;
181 | c.width += 1;
182 | }
183 | }
184 |
185 | // 里面添加圆
186 | if (mRipples.size() > 0) {
187 | // 控制第二个圆出来的间距
188 | if (mRipples.get(mRipples.size() - 1).width == 50) {
189 | mRipples.add(new Circle(0, 255));
190 | }
191 | }
192 | invalidate();
193 | canvas.restore();
194 | }
195 |
196 | @Override
197 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
198 | super.onMeasure(widthMeasureSpec, heightMeasureSpec);
199 | int myWidthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
200 | int myWidthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
201 | int myHeightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
202 | int myHeightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
203 |
204 | // 获取宽度
205 | if (myWidthSpecMode == MeasureSpec.EXACTLY) {
206 | // match_parent
207 | mWidth = myWidthSpecSize;
208 | } else {
209 | // wrap_content
210 | mWidth = DensityUtil.dip2px(mContext, 120);
211 | }
212 |
213 | // 获取高度
214 | if (myHeightSpecMode == MeasureSpec.EXACTLY) {
215 | mHeight = myHeightSpecSize;
216 | } else {
217 | // wrap_content
218 | mHeight = DensityUtil.dip2px(mContext, 120);
219 | }
220 |
221 | // 设置该view的宽高
222 | setMeasuredDimension((int) mWidth, (int) mHeight);
223 | }
224 |
225 |
226 | class Circle {
227 | Circle(int width, int alpha) {
228 | this.width = width;
229 | this.alpha = alpha;
230 | }
231 |
232 | int width;
233 |
234 | int alpha;
235 | }
236 |
237 | }
--------------------------------------------------------------------------------