├── app
├── .gitignore
├── src
│ ├── main
│ │ ├── res
│ │ │ ├── drawable
│ │ │ │ ├── icon_restart.png
│ │ │ │ └── ic_launcher_background.xml
│ │ │ ├── mipmap-hdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ └── ic_launcher_round.png
│ │ │ ├── mipmap-mdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ └── ic_launcher_round.png
│ │ │ ├── mipmap-xhdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ └── ic_launcher_round.png
│ │ │ ├── mipmap-xxhdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ └── ic_launcher_round.png
│ │ │ ├── mipmap-xxxhdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ └── ic_launcher_round.png
│ │ │ ├── values
│ │ │ │ ├── strings.xml
│ │ │ │ ├── colors.xml
│ │ │ │ └── styles.xml
│ │ │ ├── mipmap-anydpi-v26
│ │ │ │ ├── ic_launcher.xml
│ │ │ │ └── ic_launcher_round.xml
│ │ │ ├── menu
│ │ │ │ └── menu_item.xml
│ │ │ ├── layout
│ │ │ │ ├── move_activity.xml
│ │ │ │ ├── toolbar.xml
│ │ │ │ ├── activity_main.xml
│ │ │ │ └── left_drawer.xml
│ │ │ └── drawable-v24
│ │ │ │ └── ic_launcher_foreground.xml
│ │ ├── java
│ │ │ └── com
│ │ │ │ └── demo
│ │ │ │ └── meispiderweb
│ │ │ │ ├── MoveActivity.java
│ │ │ │ ├── SpiderPoint.java
│ │ │ │ ├── SpiderConfig.java
│ │ │ │ ├── MoveView.java
│ │ │ │ ├── MainActivity.java
│ │ │ │ └── SpiderWebView.java
│ │ └── AndroidManifest.xml
│ ├── test
│ │ └── java
│ │ │ └── com
│ │ │ └── demo
│ │ │ └── meispiderweb
│ │ │ └── ExampleUnitTest.java
│ └── androidTest
│ │ └── java
│ │ └── com
│ │ └── demo
│ │ └── meispiderweb
│ │ └── ExampleInstrumentedTest.java
├── proguard-rules.pro
└── build.gradle
├── settings.gradle
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── .idea
├── caches
│ └── build_file_checksums.ser
├── runConfigurations.xml
├── gradle.xml
├── misc.xml
└── codeStyles
│ └── Project.xml
├── .gitignore
├── gradle.properties
├── gradlew.bat
├── gradlew
└── README.md
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app'
2 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HpWens/SpiderWebView/HEAD/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/.idea/caches/build_file_checksums.ser:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HpWens/SpiderWebView/HEAD/.idea/caches/build_file_checksums.ser
--------------------------------------------------------------------------------
/app/src/main/res/drawable/icon_restart.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HpWens/SpiderWebView/HEAD/app/src/main/res/drawable/icon_restart.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HpWens/SpiderWebView/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HpWens/SpiderWebView/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HpWens/SpiderWebView/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HpWens/SpiderWebView/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HpWens/SpiderWebView/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HpWens/SpiderWebView/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HpWens/SpiderWebView/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HpWens/SpiderWebView/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HpWens/SpiderWebView/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HpWens/SpiderWebView/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea/libraries
5 | /.idea/modules.xml
6 | /.idea/workspace.xml
7 | .DS_Store
8 | /build
9 | /captures
10 | .externalNativeBuild
11 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | MeiSpiderWeb
3 | 开
4 | 关
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #3F51B5
4 | #303F9F
5 | #FF4081
6 |
7 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Tue Mar 26 09:04:16 CST 2019
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-all.zip
7 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/menu_item.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/src/test/java/com/demo/meispiderweb/ExampleUnitTest.java:
--------------------------------------------------------------------------------
1 | package com.demo.meispiderweb;
2 |
3 | import org.junit.Test;
4 |
5 | import static org.junit.Assert.*;
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * @see Testing documentation
11 | */
12 | public class ExampleUnitTest {
13 | @Test
14 | public void addition_isCorrect() {
15 | assertEquals(4, 2 + 2);
16 | }
17 | }
--------------------------------------------------------------------------------
/app/src/main/res/layout/move_activity.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/toolbar.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/src/main/java/com/demo/meispiderweb/MoveActivity.java:
--------------------------------------------------------------------------------
1 | package com.demo.meispiderweb;
2 |
3 | import android.os.Bundle;
4 | import android.support.annotation.Nullable;
5 | import android.support.v7.app.AppCompatActivity;
6 |
7 | /**
8 | * Created by wenshi on 2019/3/26.
9 | * Description
10 | */
11 | public class MoveActivity extends AppCompatActivity {
12 |
13 | @Override
14 | protected void onCreate(@Nullable Bundle savedInstanceState) {
15 | super.onCreate(savedInstanceState);
16 | setContentView(R.layout.move_activity);
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/.idea/runConfigurations.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
11 |
12 |
--------------------------------------------------------------------------------
/.idea/gradle.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 | # IDE (e.g. Android Studio) users:
3 | # Gradle settings configured through the IDE *will override*
4 | # any settings specified in this file.
5 | # For more details on how to configure your build environment visit
6 | # http://www.gradle.org/docs/current/userguide/build_environment.html
7 | # Specifies the JVM arguments used for the daemon process.
8 | # The setting is particularly useful for tweaking memory settings.
9 | org.gradle.jvmargs=-Xmx1536m
10 | # When configured, Gradle will run in incubating parallel mode.
11 | # This option should only be used with decoupled projects. More details, visit
12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
13 | # org.gradle.parallel=true
14 |
--------------------------------------------------------------------------------
/app/src/main/java/com/demo/meispiderweb/SpiderPoint.java:
--------------------------------------------------------------------------------
1 | package com.demo.meispiderweb;
2 |
3 | import android.graphics.Point;
4 |
5 | /**
6 | * Created by wenshi on 2019/3/26.
7 | * Description https://github.com/HpWens/MeiWidgetView
8 | */
9 | public class SpiderPoint extends Point {
10 |
11 | // x 方向加速度
12 | public int aX;
13 |
14 | // y 方向加速度
15 | public int aY;
16 |
17 | // 小球颜色
18 | public int color;
19 |
20 | // 小球半径
21 | public int r;
22 |
23 | // x 轴方向速度
24 | public float vX;
25 |
26 | // y 轴方向速度
27 | public float vY;
28 |
29 | // 点
30 | // public float x;
31 | // public float y;
32 |
33 |
34 | public SpiderPoint(int x, int y) {
35 | super(x, y);
36 | }
37 |
38 | public SpiderPoint() {
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
22 |
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
12 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/com/demo/meispiderweb/ExampleInstrumentedTest.java:
--------------------------------------------------------------------------------
1 | package com.demo.meispiderweb;
2 |
3 | import android.content.Context;
4 | import android.support.test.InstrumentationRegistry;
5 | import android.support.test.runner.AndroidJUnit4;
6 |
7 | import org.junit.Test;
8 | import org.junit.runner.RunWith;
9 |
10 | import static org.junit.Assert.*;
11 |
12 | /**
13 | * Instrumented test, which will execute on an Android device.
14 | *
15 | * @see Testing documentation
16 | */
17 | @RunWith(AndroidJUnit4.class)
18 | public class ExampleInstrumentedTest {
19 | @Test
20 | public void useAppContext() {
21 | // Context of the app under test.
22 | Context appContext = InstrumentationRegistry.getTargetContext();
23 |
24 | assertEquals("com.demo.meispiderweb", appContext.getPackageName());
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | android {
4 | compileSdkVersion 28
5 | defaultConfig {
6 | applicationId "com.demo.meispiderweb"
7 | minSdkVersion 15
8 | targetSdkVersion 28
9 | versionCode 1
10 | versionName "1.0"
11 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
12 | }
13 | buildTypes {
14 | release {
15 | minifyEnabled false
16 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
17 | }
18 | }
19 | }
20 |
21 | dependencies {
22 | implementation fileTree(dir: 'libs', include: ['*.jar'])
23 | implementation 'com.android.support:appcompat-v7:28.0.0'
24 | implementation 'com.android.support.constraint:constraint-layout:1.1.3'
25 | testImplementation 'junit:junit:4.12'
26 | androidTestImplementation 'com.android.support.test:runner:1.0.2'
27 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
28 | }
29 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
11 |
12 |
16 |
17 |
21 |
22 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/app/src/main/java/com/demo/meispiderweb/SpiderConfig.java:
--------------------------------------------------------------------------------
1 | package com.demo.meispiderweb;
2 |
3 | /**
4 | * Created by wenshi on 2019/3/26.
5 | * Description https://github.com/HpWens/MeiWidgetView
6 | */
7 | public class SpiderConfig {
8 |
9 | // 小点半径
10 | public int pointRadius = DEFAULT_POINT_RADIUS;
11 |
12 | // 小点之间连线的粗细(宽度)
13 | public int lineWidth = DEFAULT_LINE_WIDTH;
14 |
15 | // 小点之间连线的透明度
16 | public int lineAlpha = DEFAULT_LINE_ALPHA;
17 |
18 | // 小点数量
19 | public int pointNum = DEFAULT_POINT_NUMBER;
20 |
21 | // 小点加速度
22 | public int pointAcceleration = DEFAULT_POINT_ACCELERATION;
23 |
24 | // 小点之间最长直线距离
25 | public int maxDistance = DEFAULT_MAX_DISTANCE;
26 |
27 | // 触摸点半径
28 | public int touchPointRadius = DEFAULT_TOUCH_POINT_RADIUS;
29 |
30 | // 引力大小
31 | public int gravitation_strength = DEFAULT_GRAVITATION_STRENGTH;
32 |
33 |
34 | public static int DEFAULT_POINT_RADIUS = 1;
35 |
36 | public static int DEFAULT_LINE_WIDTH = 2;
37 |
38 | public static int DEFAULT_LINE_ALPHA = 150;
39 |
40 | // ~ 160
41 | public static int DEFAULT_POINT_NUMBER = 50;
42 |
43 | public static int DEFAULT_POINT_ACCELERATION = 7;
44 |
45 | public static int DEFAULT_MAX_DISTANCE = 280;
46 |
47 | public static int DEFAULT_TOUCH_POINT_RADIUS = 1;
48 |
49 | public static int DEFAULT_GRAVITATION_STRENGTH = 50;
50 |
51 | }
52 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/.idea/codeStyles/Project.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable-v24/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
7 |
12 |
13 |
19 |
22 |
25 |
26 |
27 |
28 |
34 |
35 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | set DIRNAME=%~dp0
12 | if "%DIRNAME%" == "" set DIRNAME=.
13 | set APP_BASE_NAME=%~n0
14 | set APP_HOME=%DIRNAME%
15 |
16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
17 | set DEFAULT_JVM_OPTS=
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windows variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 |
53 | :win9xME_args
54 | @rem Slurp the command line arguments.
55 | set CMD_LINE_ARGS=
56 | set _SKIP=2
57 |
58 | :win9xME_args_slurp
59 | if "x%~1" == "x" goto execute
60 |
61 | set CMD_LINE_ARGS=%*
62 |
63 | :execute
64 | @rem Setup the command line
65 |
66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
67 |
68 | @rem Execute Gradle
69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
70 |
71 | :end
72 | @rem End local scope for the variables with windows NT shell
73 | if "%ERRORLEVEL%"=="0" goto mainEnd
74 |
75 | :fail
76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
77 | rem the _cmd.exe /c_ return code!
78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
79 | exit /b 1
80 |
81 | :mainEnd
82 | if "%OS%"=="Windows_NT" endlocal
83 |
84 | :omega
85 |
--------------------------------------------------------------------------------
/app/src/main/java/com/demo/meispiderweb/MoveView.java:
--------------------------------------------------------------------------------
1 | package com.demo.meispiderweb;
2 |
3 | import android.content.Context;
4 | import android.graphics.Canvas;
5 | import android.graphics.Color;
6 | import android.graphics.Paint;
7 | import android.graphics.Point;
8 | import android.support.annotation.Nullable;
9 | import android.util.AttributeSet;
10 | import android.view.MotionEvent;
11 | import android.view.View;
12 |
13 | import java.util.Random;
14 |
15 | /**
16 | * Created by wenshi on 2019/3/26.
17 | * Description
18 | */
19 | public class MoveView extends View {
20 |
21 | // 画笔
22 | private Paint mPointPaint;
23 | // 蛛网点对象(类似小球)
24 | private SpiderPoint mSpiderPoint;
25 | // 坐标系
26 | private Point mCoordinate;
27 |
28 | // 蛛网点 默认小球半径
29 | private int pointRadius = 20;
30 | // 默认颜色
31 | private int pointColor = Color.RED;
32 | // 默认x方向速度
33 | private float pointVX = 10;
34 | // 默认y方向速度
35 | private float pointVY = 6;
36 | // 默认 小球加速度
37 | private int pointAX = 0;
38 | private int pointAY = 0;
39 |
40 | // 是否开始运动
41 | private boolean startMove = false;
42 |
43 | public MoveView(Context context) {
44 | this(context, null);
45 | }
46 |
47 | public MoveView(Context context, @Nullable AttributeSet attrs) {
48 | this(context, attrs, 0);
49 | }
50 |
51 | public MoveView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
52 | super(context, attrs, defStyleAttr);
53 | initData();
54 | initPaint();
55 | }
56 |
57 | private void initData() {
58 | mCoordinate = new Point(500, 500);
59 | mSpiderPoint = new SpiderPoint();
60 | mSpiderPoint.color = pointColor;
61 | mSpiderPoint.vX = pointVX;
62 | mSpiderPoint.vY = pointVY;
63 | mSpiderPoint.aX = pointAX;
64 | mSpiderPoint.aY = pointAY;
65 | mSpiderPoint.r = pointRadius;
66 | }
67 |
68 | // 初始化画笔
69 | private void initPaint() {
70 | mPointPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
71 | mPointPaint.setColor(pointColor);
72 | }
73 |
74 | @Override
75 | protected void onDraw(Canvas canvas) {
76 | super.onDraw(canvas);
77 |
78 | canvas.save();
79 | canvas.translate(mCoordinate.x, mCoordinate.y);
80 | drawSpiderPoint(canvas, mSpiderPoint);
81 |
82 | canvas.drawLine(400 + mSpiderPoint.r, -500, 400 + mSpiderPoint.r, getHeight(), mPointPaint);
83 |
84 | canvas.drawLine(-400 - mSpiderPoint.r, -500, -400 - mSpiderPoint.r, getHeight(), mPointPaint);
85 |
86 | canvas.drawLine(-400 - mSpiderPoint.r, -400, 400 + mSpiderPoint.r, -400, mPointPaint);
87 |
88 | canvas.drawLine(-400 - mSpiderPoint.r, 400, 400 + mSpiderPoint.r, 400, mPointPaint);
89 |
90 | canvas.restore();
91 |
92 | // 刷新视图 再次调用onDraw方法模拟时间流
93 | if (startMove) {
94 | updateBall();
95 | invalidate();
96 | }
97 |
98 |
99 | }
100 |
101 | /**
102 | * 绘制蛛网点
103 | *
104 | * @param canvas
105 | * @param spiderPoint
106 | */
107 | private void drawSpiderPoint(Canvas canvas, SpiderPoint spiderPoint) {
108 | mPointPaint.setColor(spiderPoint.color);
109 | canvas.drawCircle(spiderPoint.x, spiderPoint.y, spiderPoint.r, mPointPaint);
110 | }
111 |
112 | /**
113 | * 更新小球
114 | */
115 | private void updateBall() {
116 | //TODO --运动数据都由此函数变换
117 | mSpiderPoint.x += mSpiderPoint.vX;
118 | mSpiderPoint.y += mSpiderPoint.vY;
119 | if (mSpiderPoint.x > 400) {
120 | // 更改颜色
121 | mSpiderPoint.color = randomRGB();
122 | mSpiderPoint.vX = -mSpiderPoint.vX;
123 | }
124 | if (mSpiderPoint.x < -400) {
125 | mSpiderPoint.vX = -mSpiderPoint.vX;
126 | // 更改颜色
127 | mSpiderPoint.color = randomRGB();
128 | }
129 |
130 | if (mSpiderPoint.y > 400) {
131 | // 更改颜色
132 | mSpiderPoint.color = randomRGB();
133 | mSpiderPoint.vY = -mSpiderPoint.vY;
134 | }
135 | if (mSpiderPoint.y < -400) {
136 | mSpiderPoint.vY = -mSpiderPoint.vY;
137 | // 更改颜色
138 | mSpiderPoint.color = randomRGB();
139 | }
140 | }
141 |
142 | @Override
143 | public boolean onTouchEvent(MotionEvent event) {
144 | switch (event.getAction()) {
145 | case MotionEvent.ACTION_DOWN:
146 | // 开启时间流
147 | startMove = true;
148 | invalidate();
149 | break;
150 | case MotionEvent.ACTION_UP:
151 | // 暂停时间流
152 | // startMove = false;
153 | // invalidate();
154 | break;
155 | }
156 | return true;
157 | }
158 |
159 | /**
160 | * 两点间距离函数
161 | */
162 | public static int disPos2d(float x1, float y1, float x2, float y2) {
163 | return (int) Math.sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2));
164 | }
165 |
166 | /**
167 | * 获取范围随机整数:如 rangeInt(1,9)
168 | *
169 | * @param s 前数(包括)
170 | * @param e 后数(包括)
171 | * @return 范围随机整数
172 | */
173 | public static int rangeInt(int s, int e) {
174 | int max = Math.max(s, e);
175 | int min = Math.min(s, e) - 1;
176 | return (int) (min + Math.ceil(Math.random() * (max - min)));
177 | }
178 |
179 | /**
180 | * @return 获取到随机颜色值
181 | */
182 | private int randomRGB() {
183 | Random random = new Random();
184 | return Color.rgb(random.nextInt(255), random.nextInt(255), random.nextInt(255));
185 | }
186 | }
187 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/left_drawer.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
17 |
18 |
27 |
28 |
34 |
35 |
44 |
45 |
51 |
52 |
61 |
62 |
68 |
69 |
78 |
79 |
85 |
86 |
95 |
96 |
102 |
103 |
112 |
113 |
119 |
120 |
129 |
130 |
136 |
137 |
146 |
147 |
153 |
154 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Attempt to set APP_HOME
10 | # Resolve links: $0 may be a link
11 | PRG="$0"
12 | # Need this for relative symlinks.
13 | while [ -h "$PRG" ] ; do
14 | ls=`ls -ld "$PRG"`
15 | link=`expr "$ls" : '.*-> \(.*\)$'`
16 | if expr "$link" : '/.*' > /dev/null; then
17 | PRG="$link"
18 | else
19 | PRG=`dirname "$PRG"`"/$link"
20 | fi
21 | done
22 | SAVED="`pwd`"
23 | cd "`dirname \"$PRG\"`/" >/dev/null
24 | APP_HOME="`pwd -P`"
25 | cd "$SAVED" >/dev/null
26 |
27 | APP_NAME="Gradle"
28 | APP_BASE_NAME=`basename "$0"`
29 |
30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
31 | DEFAULT_JVM_OPTS=""
32 |
33 | # Use the maximum available, or set MAX_FD != -1 to use that value.
34 | MAX_FD="maximum"
35 |
36 | warn () {
37 | echo "$*"
38 | }
39 |
40 | die () {
41 | echo
42 | echo "$*"
43 | echo
44 | exit 1
45 | }
46 |
47 | # OS specific support (must be 'true' or 'false').
48 | cygwin=false
49 | msys=false
50 | darwin=false
51 | nonstop=false
52 | case "`uname`" in
53 | CYGWIN* )
54 | cygwin=true
55 | ;;
56 | Darwin* )
57 | darwin=true
58 | ;;
59 | MINGW* )
60 | msys=true
61 | ;;
62 | NONSTOP* )
63 | nonstop=true
64 | ;;
65 | esac
66 |
67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
68 |
69 | # Determine the Java command to use to start the JVM.
70 | if [ -n "$JAVA_HOME" ] ; then
71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
72 | # IBM's JDK on AIX uses strange locations for the executables
73 | JAVACMD="$JAVA_HOME/jre/sh/java"
74 | else
75 | JAVACMD="$JAVA_HOME/bin/java"
76 | fi
77 | if [ ! -x "$JAVACMD" ] ; then
78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
79 |
80 | Please set the JAVA_HOME variable in your environment to match the
81 | location of your Java installation."
82 | fi
83 | else
84 | JAVACMD="java"
85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
86 |
87 | Please set the JAVA_HOME variable in your environment to match the
88 | location of your Java installation."
89 | fi
90 |
91 | # Increase the maximum file descriptors if we can.
92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
93 | MAX_FD_LIMIT=`ulimit -H -n`
94 | if [ $? -eq 0 ] ; then
95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
96 | MAX_FD="$MAX_FD_LIMIT"
97 | fi
98 | ulimit -n $MAX_FD
99 | if [ $? -ne 0 ] ; then
100 | warn "Could not set maximum file descriptor limit: $MAX_FD"
101 | fi
102 | else
103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
104 | fi
105 | fi
106 |
107 | # For Darwin, add options to specify how the application appears in the dock
108 | if $darwin; then
109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
110 | fi
111 |
112 | # For Cygwin, switch paths to Windows format before running java
113 | if $cygwin ; then
114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
116 | JAVACMD=`cygpath --unix "$JAVACMD"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Escape application args
158 | save () {
159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
160 | echo " "
161 | }
162 | APP_ARGS=$(save "$@")
163 |
164 | # Collect all arguments for the java command, following the shell quoting and substitution rules
165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
166 |
167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
169 | cd "$(dirname "$0")"
170 | fi
171 |
172 | exec "$JAVACMD" "$@"
173 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
10 |
15 |
20 |
25 |
30 |
35 |
40 |
45 |
50 |
55 |
60 |
65 |
70 |
75 |
80 |
85 |
90 |
95 |
100 |
105 |
110 |
115 |
120 |
125 |
130 |
135 |
140 |
145 |
150 |
155 |
160 |
165 |
170 |
171 |
--------------------------------------------------------------------------------
/app/src/main/java/com/demo/meispiderweb/MainActivity.java:
--------------------------------------------------------------------------------
1 | package com.demo.meispiderweb;
2 |
3 | import android.os.Bundle;
4 | import android.support.v4.widget.DrawerLayout;
5 | import android.support.v7.app.ActionBarDrawerToggle;
6 | import android.support.v7.app.AppCompatActivity;
7 | import android.support.v7.widget.Toolbar;
8 | import android.view.Menu;
9 | import android.view.MenuItem;
10 | import android.view.MotionEvent;
11 | import android.view.View;
12 | import android.widget.SeekBar;
13 | import android.widget.TextView;
14 |
15 | public class MainActivity extends AppCompatActivity implements View.OnTouchListener, SeekBar.OnSeekBarChangeListener {
16 |
17 | private SpiderWebView mSpiderWebView;
18 | private DrawerLayout mDrawerLayout;
19 | private ActionBarDrawerToggle mDrawerToggle;
20 | private Toolbar mToolbar;
21 | private SeekBar mSeekbar1;
22 | private SeekBar mSeekbar2;
23 | private SeekBar mSeekbar3;
24 | private SeekBar mSeekbar4;
25 | private TextView mTextView1;
26 | private TextView mTextView2;
27 | private TextView mTextView3;
28 | private TextView mTextView4;
29 | private TextView mTextView5;
30 | private TextView mTextView6;
31 | private TextView mTextView7;
32 | private TextView mTextView8;
33 | private SeekBar mSeekbar5;
34 | private SeekBar mSeekbar6;
35 | private SeekBar mSeekbar7;
36 | private SeekBar mSeekbar8;
37 |
38 | @Override
39 | protected void onCreate(Bundle savedInstanceState) {
40 | super.onCreate(savedInstanceState);
41 | setContentView(R.layout.activity_main);
42 |
43 | initView();
44 | initListener();
45 | mSeekbar1.setProgress(49);
46 | mSeekbar2.setProgress(4);
47 | mSeekbar3.setProgress(250);
48 | mSeekbar4.setProgress(150);
49 | mSeekbar5.setProgress(2);
50 | mSeekbar6.setProgress(1);
51 | mSeekbar7.setProgress(1);
52 | mSeekbar8.setProgress(50);
53 | }
54 |
55 | private void initView() {
56 | mSpiderWebView = (SpiderWebView) findViewById(R.id.cob_web_view);
57 | mTextView1 = (TextView) findViewById(R.id.textview1);
58 | mTextView2 = (TextView) findViewById(R.id.textview2);
59 | mTextView3 = (TextView) findViewById(R.id.textview3);
60 | mTextView4 = (TextView) findViewById(R.id.textview4);
61 | mTextView5 = (TextView) findViewById(R.id.textview5);
62 | mTextView6 = (TextView) findViewById(R.id.textview6);
63 | mTextView7 = (TextView) findViewById(R.id.textview7);
64 | mTextView8 = (TextView) findViewById(R.id.textview8);
65 | mSeekbar1 = (SeekBar) findViewById(R.id.seekbar1);
66 | mSeekbar2 = (SeekBar) findViewById(R.id.seekbar2);
67 | mSeekbar3 = (SeekBar) findViewById(R.id.seekbar3);
68 | mSeekbar4 = (SeekBar) findViewById(R.id.seekbar4);
69 | mSeekbar5 = (SeekBar) findViewById(R.id.seekbar5);
70 | mSeekbar6 = (SeekBar) findViewById(R.id.seekbar6);
71 | mSeekbar7 = (SeekBar) findViewById(R.id.seekbar7);
72 | mSeekbar8 = (SeekBar) findViewById(R.id.seekbar8);
73 | mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
74 |
75 | mToolbar = (Toolbar) findViewById(R.id.toolbar);
76 |
77 | setSupportActionBar(mToolbar);
78 | getSupportActionBar().setDisplayHomeAsUpEnabled(true);
79 | mDrawerToggle = new ActionBarDrawerToggle(this, mDrawerLayout, mToolbar, R.string.drawer_open, R.string.drawer_close);
80 | mDrawerToggle.syncState();
81 | }
82 |
83 | private void initListener() {
84 | mToolbar.setOnMenuItemClickListener(new Toolbar.OnMenuItemClickListener() {
85 | @Override
86 | public boolean onMenuItemClick(MenuItem item) {
87 | switch (item.getItemId()) {
88 | case R.id.menu_item1:
89 | mSpiderWebView.restart();
90 | return true;
91 | }
92 | return false;
93 | }
94 | });
95 | mSeekbar1.setOnTouchListener(this);
96 | mSeekbar2.setOnTouchListener(this);
97 | mSeekbar3.setOnTouchListener(this);
98 | mSeekbar4.setOnTouchListener(this);
99 | mSeekbar5.setOnTouchListener(this);
100 | mSeekbar6.setOnTouchListener(this);
101 | mSeekbar7.setOnTouchListener(this);
102 | mSeekbar8.setOnTouchListener(this);
103 | mSeekbar1.setOnSeekBarChangeListener(this);
104 | mSeekbar2.setOnSeekBarChangeListener(this);
105 | mSeekbar3.setOnSeekBarChangeListener(this);
106 | mSeekbar4.setOnSeekBarChangeListener(this);
107 | mSeekbar5.setOnSeekBarChangeListener(this);
108 | mSeekbar6.setOnSeekBarChangeListener(this);
109 | mSeekbar7.setOnSeekBarChangeListener(this);
110 | mSeekbar8.setOnSeekBarChangeListener(this);
111 | mDrawerLayout.addDrawerListener(new DrawerLayout.DrawerListener() {
112 | @Override
113 | public void onDrawerSlide(View drawerView, float slideOffset) {
114 | mDrawerToggle.onDrawerSlide(drawerView, slideOffset);
115 | }
116 |
117 | @Override
118 | public void onDrawerOpened(View drawerView) {
119 | mDrawerToggle.onDrawerOpened(drawerView);
120 | }
121 |
122 | @Override
123 | public void onDrawerClosed(View drawerView) {
124 | mDrawerToggle.onDrawerClosed(drawerView);
125 | }
126 |
127 | @Override
128 | public void onDrawerStateChanged(int newState) {
129 | mSpiderWebView.resetTouchPoint();
130 | mDrawerToggle.onDrawerStateChanged(newState);
131 | }
132 | });
133 | }
134 |
135 | @Override
136 | public boolean onCreateOptionsMenu(Menu menu) {
137 | getMenuInflater().inflate(R.menu.menu_item, menu);//加载menu文件到布局
138 | return true;
139 | }
140 |
141 | @Override
142 | public boolean onTouch(View v, MotionEvent event) {
143 | if (v instanceof SeekBar)
144 | mDrawerLayout.requestDisallowInterceptTouchEvent(true);
145 | return false;
146 | }
147 |
148 | @Override
149 | public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
150 | switch (seekBar.getId()) {
151 | case R.id.seekbar1:
152 | mSpiderWebView.setPointNum(progress + 1);
153 | mTextView1.setText(String.format("小点数量: %d", mSpiderWebView.getPointNum()));
154 | break;
155 | case R.id.seekbar2:
156 | mSpiderWebView.setPointAcceleration(progress + 3);
157 | mTextView2.setText(String.format("加速度: %d", mSpiderWebView.getPointAcceleration()));
158 | break;
159 | case R.id.seekbar3:
160 | mSpiderWebView.setMaxDistance(progress + 20);
161 | mTextView3.setText(String.format("最大连线距离: %d", mSpiderWebView.getMaxDistance()));
162 | break;
163 | case R.id.seekbar4:
164 | mSpiderWebView.setLineAlpha(progress);
165 | mTextView4.setText(String.format("连线透明度: %d", mSpiderWebView.getLineAlpha()));
166 | break;
167 | case R.id.seekbar5:
168 | mSpiderWebView.setLineWidth(progress);
169 | mTextView5.setText(String.format("连线粗细: %d", mSpiderWebView.getLineWidth()));
170 | break;
171 | case R.id.seekbar6:
172 | mSpiderWebView.setPointRadius(progress);
173 | mTextView6.setText(String.format("小点半径: %d", mSpiderWebView.getPointRadius()));
174 | break;
175 | case R.id.seekbar7:
176 | mSpiderWebView.setTouchPointRadius(progress);
177 | mTextView7.setText(String.format("触摸点半径: %d", mSpiderWebView.getTouchPointRadius()));
178 | break;
179 | case R.id.seekbar8:
180 | mSpiderWebView.setGravitation_strength(progress);
181 | mTextView8.setText(String.format("引力强度: %d", mSpiderWebView.getGravitation_strength()));
182 | break;
183 | }
184 | }
185 |
186 | @Override
187 | public void onStartTrackingTouch(SeekBar seekBar) {
188 |
189 | }
190 |
191 | @Override
192 | public void onStopTrackingTouch(SeekBar seekBar) {
193 |
194 | }
195 |
196 | @Override
197 | public void onPointerCaptureChanged(boolean hasCapture) {
198 |
199 | }
200 | }
201 |
--------------------------------------------------------------------------------
/app/src/main/java/com/demo/meispiderweb/SpiderWebView.java:
--------------------------------------------------------------------------------
1 | package com.demo.meispiderweb;
2 |
3 | import android.content.Context;
4 | import android.graphics.Canvas;
5 | import android.graphics.Color;
6 | import android.graphics.Paint;
7 | import android.support.annotation.Nullable;
8 | import android.util.AttributeSet;
9 | import android.view.GestureDetector;
10 | import android.view.MotionEvent;
11 | import android.view.View;
12 |
13 | import java.util.ArrayList;
14 | import java.util.List;
15 | import java.util.Random;
16 |
17 | /**
18 | * Created by wenshi on 2019/3/26.
19 | * Description https://github.com/HpWens/MeiWidgetView
20 | */
21 | public class SpiderWebView extends View {
22 | // 控件宽高
23 | private int mWidth;
24 | private int mHeight;
25 | // 画笔
26 | private Paint mPointPaint;
27 | private Paint mLinePaint;
28 | private Paint mTouchPaint;
29 | // 触摸点坐标
30 | private float mTouchX = -1;
31 | private float mTouchY = -1;
32 | // 数据源
33 | private List mSpiderPointList;
34 | // 相关参数配置
35 | private SpiderConfig mConfig;
36 | // 随机数
37 | private Random mRandom;
38 | // 手势帮助类 用于处理滚动与拖拽
39 | private GestureDetector mGestureDetector;
40 |
41 | public SpiderWebView(Context context) {
42 | this(context, null);
43 | }
44 |
45 | public SpiderWebView(Context context, @Nullable AttributeSet attrs) {
46 | this(context, attrs, 0);
47 | }
48 |
49 | public SpiderWebView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
50 | super(context, attrs, defStyleAttr);
51 | // setLayerType(LAYER_TYPE_HARDWARE, null);
52 |
53 | mSpiderPointList = new ArrayList<>();
54 | mConfig = new SpiderConfig();
55 | mRandom = new Random();
56 | mGestureDetector = new GestureDetector(context, mSimpleOnGestureListener);
57 |
58 | // 画笔初始化
59 | initPaint();
60 | }
61 |
62 | /**
63 | * 初始化画笔
64 | */
65 | private void initPaint() {
66 | mPointPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
67 | mPointPaint.setStrokeCap(Paint.Cap.ROUND);
68 | mPointPaint.setStrokeWidth(mConfig.pointRadius);
69 | mPointPaint.setColor(Color.parseColor("#EBFF4081"));
70 |
71 | mLinePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
72 | mLinePaint.setStrokeWidth(mConfig.lineWidth);
73 | mLinePaint.setStrokeCap(Paint.Cap.ROUND);
74 | mLinePaint.setColor(Color.parseColor("#EBFF94B9"));
75 |
76 | mTouchPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
77 | mTouchPaint.setStrokeWidth(mConfig.touchPointRadius);
78 | mTouchPaint.setStrokeCap(Paint.Cap.ROUND);
79 | mTouchPaint.setColor(Color.parseColor("#D8FF7875"));
80 | }
81 |
82 | /**
83 | * 初始化小点
84 | */
85 | private void initPoint() {
86 | for (int i = 0; i < mConfig.pointNum; i++) {
87 | int width = (int) (mRandom.nextFloat() * mWidth);
88 | int height = (int) (mRandom.nextFloat() * mHeight);
89 |
90 | SpiderPoint point = new SpiderPoint(width, height);
91 |
92 | int aX = 0;
93 | int aY = 0;
94 |
95 | // 获取加速度
96 | while (aX == 0) {
97 | aX = (int) ((mRandom.nextFloat() - 0.5F) * mConfig.pointAcceleration);
98 | }
99 | while (aY == 0) {
100 | aY = (int) ((mRandom.nextFloat() - 0.5F) * mConfig.pointAcceleration);
101 | }
102 |
103 | point.aX = aX;
104 | point.aY = aY;
105 |
106 | point.color = randomRGB();
107 |
108 | mSpiderPointList.add(point);
109 | }
110 | }
111 |
112 | @Override
113 | protected void onSizeChanged(int w, int h, int oldw, int oldh) {
114 | super.onSizeChanged(w, h, oldw, oldh);
115 | mWidth = w;
116 | mHeight = h;
117 | restart();
118 | }
119 |
120 | @Override
121 | protected void onDraw(Canvas canvas) {
122 | super.onDraw(canvas);
123 | canvas.save();
124 | // 绘制触摸点
125 | if (mTouchY != -1 && mTouchX != -1) {
126 | canvas.drawPoint(mTouchX, mTouchY, mTouchPaint);
127 | }
128 |
129 | if (mSpiderPointList == null || mSpiderPointList.size() <= 0) {
130 | return;
131 | }
132 |
133 | // 增强遍历
134 | int index = 0;
135 | for (SpiderPoint spiderPoint : mSpiderPointList) {
136 |
137 | spiderPoint.x += spiderPoint.aX;
138 | spiderPoint.y += spiderPoint.aY;
139 |
140 | // 越界反弹
141 | if (spiderPoint.x <= mConfig.pointRadius) {
142 | spiderPoint.x = mConfig.pointRadius;
143 | spiderPoint.aX = -spiderPoint.aX;
144 | } else if (spiderPoint.x >= (mWidth - mConfig.pointRadius)) {
145 | spiderPoint.x = (mWidth - mConfig.pointRadius);
146 | spiderPoint.aX = -spiderPoint.aX;
147 | }
148 |
149 | if (spiderPoint.y <= mConfig.pointRadius) {
150 | spiderPoint.y = mConfig.pointRadius;
151 | spiderPoint.aY = -spiderPoint.aY;
152 | } else if (spiderPoint.y >= (mHeight - mConfig.pointRadius)) {
153 | spiderPoint.y = (mHeight - mConfig.pointRadius);
154 | spiderPoint.aY = -spiderPoint.aY;
155 | }
156 |
157 | // 绘制触摸点与其他点的连线
158 | if (mTouchX != -1 && mTouchY != -1) {
159 | int offsetX = (int) (mTouchX - spiderPoint.x);
160 | int offsetY = (int) (mTouchY - spiderPoint.y);
161 | int distance = (int) Math.sqrt(offsetX * offsetX + offsetY * offsetY);
162 | if (distance < mConfig.maxDistance) {
163 |
164 | if (distance >= (mConfig.maxDistance - mConfig.gravitation_strength)) {
165 | if (spiderPoint.x > mTouchX) {
166 | spiderPoint.x -= 0.03F * -offsetX;
167 | } else {
168 | spiderPoint.x += 0.03F * offsetX;
169 | }
170 |
171 | if (spiderPoint.y > mTouchY) {
172 | spiderPoint.y -= 0.03F * -offsetY;
173 | } else {
174 | spiderPoint.y += 0.03F * offsetY;
175 | }
176 | }
177 |
178 | int alpha = (int) ((1.0F - (float) distance / mConfig.maxDistance) * mConfig.lineAlpha);
179 | mLinePaint.setColor(spiderPoint.color);
180 | mLinePaint.setAlpha(alpha);
181 | canvas.drawLine(spiderPoint.x, spiderPoint.y, mTouchX, mTouchY, mLinePaint);
182 | }
183 | }
184 |
185 | // 绘制小点
186 | mPointPaint.setColor(spiderPoint.color);
187 | canvas.drawCircle(spiderPoint.x, spiderPoint.y, mConfig.pointRadius, mPointPaint);
188 |
189 | // 绘制连线
190 | for (int i = index; i < mSpiderPointList.size(); i++) {
191 | SpiderPoint point = mSpiderPointList.get(i);
192 | // 判定当前点与其他点之间的距离
193 | if (spiderPoint != point) {
194 | int distance = disPos2d(point.x, point.y, spiderPoint.x, spiderPoint.y);
195 | if (distance < mConfig.maxDistance) {
196 | // 绘制小点间的连线
197 | int alpha = (int) ((1.0F - (float) distance / mConfig.maxDistance) * mConfig.lineAlpha);
198 |
199 | mLinePaint.setColor(point.color);
200 | mLinePaint.setAlpha(alpha);
201 | canvas.drawLine(spiderPoint.x, spiderPoint.y, point.x, point.y, mLinePaint);
202 | }
203 | }
204 | }
205 |
206 | index++;
207 | }
208 | canvas.restore();
209 |
210 | invalidate();
211 | }
212 |
213 | @Override
214 | public boolean onTouchEvent(MotionEvent event) {
215 | if (event.getAction() == MotionEvent.ACTION_UP ||
216 | event.getAction() == MotionEvent.ACTION_CANCEL) {
217 | resetTouchPoint();
218 | return true;
219 | }
220 | return mGestureDetector.onTouchEvent(event);
221 | }
222 |
223 | /**
224 | * 两点间距离函数
225 | */
226 | public static int disPos2d(float x1, float y1, float x2, float y2) {
227 | return (int) Math.sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2));
228 | }
229 |
230 | /**
231 | * 获取范围随机整数:如 rangeInt(1,9)
232 | *
233 | * @param s 前数(包括)
234 | * @param e 后数(包括)
235 | * @return 范围随机整数
236 | */
237 | public static int rangeInt(int s, int e) {
238 | int max = Math.max(s, e);
239 | int min = Math.min(s, e) - 1;
240 | return (int) (min + Math.ceil(Math.random() * (max - min)));
241 | }
242 |
243 | /**
244 | * @return 获取到随机颜色值
245 | */
246 | private int randomRGB() {
247 | Random random = new Random();
248 | return Color.rgb(random.nextInt(255), random.nextInt(255), random.nextInt(255));
249 | }
250 |
251 |
252 | // 重置数据
253 | public void restart() {
254 | resetTouchPoint();
255 | clearPointList();
256 | initPoint();
257 | }
258 |
259 | /**
260 | * 重置触摸点
261 | */
262 | public void resetTouchPoint() {
263 | mTouchX = -1;
264 | mTouchY = -1;
265 | }
266 |
267 | /**
268 | * 清空数据源
269 | */
270 | private void clearPointList() {
271 | mSpiderPointList.clear();
272 | }
273 |
274 | // 手势 用于处理滑动与拖拽
275 | private GestureDetector.SimpleOnGestureListener mSimpleOnGestureListener = new GestureDetector.SimpleOnGestureListener() {
276 | @Override
277 | public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
278 | // 单根手指操作
279 | if (e1.getPointerCount() == e2.getPointerCount() && e1.getPointerCount() == 1) {
280 | mTouchX = e2.getX();
281 | mTouchY = e2.getY();
282 | return true;
283 | }
284 | return super.onScroll(e1, e2, distanceX, distanceY);
285 | }
286 |
287 | @Override
288 | public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
289 | // 单根手指操作
290 | if (e1.getPointerCount() == e2.getPointerCount() && e1.getPointerCount() == 1) {
291 | mTouchX = e2.getX();
292 | mTouchY = e2.getY();
293 | return true;
294 | }
295 | return super.onFling(e1, e2, velocityX, velocityY);
296 | }
297 |
298 | @Override
299 | public boolean onDown(MotionEvent e) {
300 | // 赋值触摸点
301 | mTouchX = e.getX();
302 | mTouchY = e.getY();
303 | return true;
304 | }
305 |
306 | @Override
307 | public boolean onSingleTapUp(MotionEvent e) {
308 | return false;
309 | }
310 | };
311 |
312 | // 设置相关配置参数
313 | public int getPointRadius() {
314 | return mConfig.pointRadius;
315 | }
316 |
317 | public void setPointRadius(int pointRadius) {
318 | mConfig.pointRadius = pointRadius;
319 | mPointPaint.setStrokeWidth(mConfig.pointRadius);
320 | }
321 |
322 | public int getLineWidth() {
323 | return mConfig.lineWidth;
324 | }
325 |
326 | public void setLineWidth(int lineWidth) {
327 | mConfig.lineWidth = lineWidth;
328 | mLinePaint.setStrokeWidth(mConfig.lineWidth);
329 | }
330 |
331 | public int getLineAlpha() {
332 | return mConfig.lineAlpha;
333 | }
334 |
335 | public void setLineAlpha(int lineAlpha) {
336 | mConfig.lineAlpha = lineAlpha;
337 | }
338 |
339 | public int getPointNum() {
340 | return mConfig.pointNum;
341 | }
342 |
343 | public void setPointNum(int pointNum) {
344 | mConfig.pointNum = pointNum;
345 | restart();
346 | }
347 |
348 | public int getPointAcceleration() {
349 | return mConfig.pointAcceleration;
350 | }
351 |
352 | public void setPointAcceleration(int pointAcceleration) {
353 | mConfig.pointAcceleration = pointAcceleration;
354 | restart();
355 | }
356 |
357 | public int getMaxDistance() {
358 | return mConfig.maxDistance;
359 | }
360 |
361 | public void setMaxDistance(int maxDistance) {
362 | mConfig.maxDistance = maxDistance;
363 | }
364 |
365 | public int getTouchPointRadius() {
366 | return mConfig.touchPointRadius;
367 | }
368 |
369 | public void setTouchPointRadius(int touchPointRadius) {
370 | mConfig.touchPointRadius = touchPointRadius;
371 | mTouchPaint.setStrokeWidth(mConfig.touchPointRadius);
372 | }
373 |
374 | public int getGravitation_strength() {
375 | return mConfig.gravitation_strength;
376 | }
377 |
378 | public void setGravitation_strength(int gravitation_strength) {
379 | mConfig.gravitation_strength = gravitation_strength;
380 | }
381 | }
382 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # SpiderWebView
2 |
3 | ### 前言
4 | 《都挺好》迎来了大结局,相信看哭了很多人。在大结局中,所有之前让人气的牙痒痒的人设,比如 “你们太让我失望” 的苏明哲,还有妈宝男苏明成,包括一天不作就难受的苏大强,最终都成功洗白。一家人最终化解恩怨,和和气气的过日子。还有谁也喜欢《都挺好》这部剧吗?
5 |
6 | 在剧中,苏明哲同我们一样也是一名程序员,一味地迁就老爹,搞得最后差点与老婆离婚,看来程序员不能一根筋啊。转变下思维来看看网页版动态背景「五彩蛛网」是怎么实现的?
7 |
8 | 先来看看效果图:
9 |
10 | 
11 | ### 初步分析
12 |
13 | 在效果图中,可以看到许多「小点」在屏幕中匀速运动并与「邻近的点」相连,每条连线的颜色随机,「小点」触碰到屏幕边缘则回弹;还有一个效果就是,手指在屏幕中移动、拖拽,与手指触摸点连线的点向触摸点靠拢。何为「邻近的点」,与某点的距离小于特定的阈值的点称为「邻近的点」。
14 |
15 | 提到运动,「运动」在[物理学](https://baike.baidu.com/item/%E8%BF%90%E5%8A%A8/2134957?fr=aladdin)中指物体在空间中的相对位置随着时间而变化。
16 |
17 | 那么大家还记得「位移」与「速度」公式吗?
18 |
19 | ```java
20 | 位移 = 初位移 + 速度 * 时间
21 | 速度 = 初速度 + 加速度
22 | ```
23 |
24 | 时间、位移、速度、加速度构成了现代科学的运动体系。我们使用 view 来模拟物体的运动。
25 |
26 | - 时间:在 view 的 onDraw 方法中调用 invalidate 方法,达到无限刷新来模拟时间流,每次刷新间隔,记为:1U
27 |
28 | - 位移:物体在屏幕中的像素位置,每个像素距离为:1px
29 |
30 | - 速度:默认设置一个值,单位(px / U)
31 |
32 | - 加速度:默认设置一个值,单位(px / U^2)
33 |
34 | 模拟「蛛网点」物体类:
35 |
36 | ```java
37 | public class SpiderPoint extends Point {
38 |
39 | // x 方向加速度
40 | public int aX;
41 |
42 | // y 方向加速度
43 | public int aY;
44 |
45 | // 小球颜色
46 | public int color;
47 |
48 | // 小球半径
49 | public int r;
50 |
51 | // x 轴方向速度
52 | public float vX;
53 |
54 | // y 轴方向速度
55 | public float vY;
56 |
57 | // 点
58 | public float x;
59 | public float y;
60 |
61 | public SpiderPoint(int x, int y) {
62 | super(x, y);
63 | }
64 | }
65 | ```
66 |
67 | #### 蛛网点匀速直线运动
68 |
69 | 搭建测试 View,初始位置 (0,0) ,x 方向速度 10、y 方向速度 0 的蛛网点:
70 |
71 | ```java
72 | public class MoveView extends View {
73 |
74 | // 画笔
75 | private Paint mPointPaint;
76 | // 蛛网点对象(类似小球)
77 | private SpiderPoint mSpiderPoint;
78 | // 坐标系
79 | private Point mCoordinate;
80 |
81 | // 蛛网点 默认小球半径
82 | private int pointRadius = 20;
83 | // 默认颜色
84 | private int pointColor = Color.RED;
85 | // 默认x方向速度
86 | private float pointVX = 10;
87 | // 默认y方向速度
88 | private float pointVY = 0;
89 | // 默认 小球加速度
90 | private int pointAX = 0;
91 | private int pointAY = 0;
92 |
93 | // 是否开始运动
94 | private boolean startMove = false;
95 |
96 | public MoveView(Context context) {
97 | this(context, null);
98 | }
99 |
100 | public MoveView(Context context, @Nullable AttributeSet attrs) {
101 | this(context, attrs, 0);
102 | }
103 |
104 | public MoveView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
105 | super(context, attrs, defStyleAttr);
106 | initData();
107 | initPaint();
108 | }
109 |
110 | private void initData() {
111 | mCoordinate = new Point(500, 500);
112 | mSpiderPoint = new SpiderPoint();
113 | mSpiderPoint.color = pointColor;
114 | mSpiderPoint.vX = pointVX;
115 | mSpiderPoint.vY = pointVY;
116 | mSpiderPoint.aX = pointAX;
117 | mSpiderPoint.aY = pointAY;
118 | mSpiderPoint.r = pointRadius;
119 | }
120 |
121 | // 初始化画笔
122 | private void initPaint() {
123 | mPointPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
124 | mPointPaint.setColor(pointColor);
125 | }
126 |
127 | @Override
128 | protected void onDraw(Canvas canvas) {
129 | super.onDraw(canvas);
130 |
131 | canvas.save();
132 | canvas.translate(mCoordinate.x, mCoordinate.y);
133 | drawSpiderPoint(canvas, mSpiderPoint);
134 | canvas.restore();
135 |
136 | // 刷新视图 再次调用onDraw方法模拟时间流
137 | if (startMove) {
138 | updateBall();
139 | invalidate();
140 | }
141 | }
142 |
143 | /**
144 | * 绘制蛛网点
145 | *
146 | * @param canvas
147 | * @param spiderPoint
148 | */
149 | private void drawSpiderPoint(Canvas canvas, SpiderPoint spiderPoint) {
150 | mPointPaint.setColor(spiderPoint.color);
151 | canvas.drawCircle(spiderPoint.x, spiderPoint.y, spiderPoint.r, mPointPaint);
152 | }
153 |
154 | /**
155 | * 更新小球
156 | */
157 | private void updateBall() {
158 | //TODO --运动数据都由此函数变换
159 | }
160 |
161 | @Override
162 | public boolean onTouchEvent(MotionEvent event) {
163 | switch (event.getAction()) {
164 | case MotionEvent.ACTION_DOWN:
165 | // 开启时间流
166 | startMove = true;
167 | invalidate();
168 | break;
169 | case MotionEvent.ACTION_UP:
170 | // 暂停时间流
171 | startMove = false;
172 | invalidate();
173 | break;
174 | }
175 | return true;
176 | }
177 | }
178 | ```
179 |
180 | 1、水平运行运动:
181 |
182 | 
183 |
184 | 根据上文中的位移公式,`位移 = 初位移 + 速度 * 时间` ,这里的时间为 1U,更新小球位置的相关代码如下:
185 |
186 | ```java
187 | /**
188 | * 更新小球
189 | */
190 | private void updateBall() {
191 | //TODO --运动数据都由此函数变换
192 | mSpiderPoint.x += mSpiderPoint.vX;
193 | }
194 | ```
195 |
196 | 2、回弹效果
197 |
198 | 回弹,速度取反,x 轴方向大于 400 则回弹:
199 |
200 | 
201 |
202 | 3、无限回弹,回弹变色
203 |
204 | 
205 |
206 | 相关代码如下:
207 |
208 | ```java
209 | /**
210 | * 更新小球
211 | */
212 | private void updateBall() {
213 | //TODO --运动数据都由此函数变换
214 | mSpiderPoint.x += mSpiderPoint.vX;
215 | if (mSpiderPoint.x > 400) {
216 | // 更改颜色
217 | mSpiderPoint.color = randomRGB();
218 | mSpiderPoint.vX = -mSpiderPoint.vX;
219 | }
220 | if (mSpiderPoint.x < -400) {
221 | mSpiderPoint.vX = -mSpiderPoint.vX;
222 | // 更改颜色
223 | mSpiderPoint.color = randomRGB();
224 | }
225 | }
226 | ```
227 |
228 | `randomRGB` 方法的代码如下:
229 |
230 | ```java
231 | /**
232 | * @return 获取到随机颜色值
233 | */
234 | private int randomRGB() {
235 | Random random = new Random();
236 | return Color.rgb(random.nextInt(255), random.nextInt(255), random.nextInt(255));
237 | }
238 | ```
239 |
240 | 3、箱式弹跳
241 |
242 | 小球在 y 轴方向的平移与 x 轴方向的平移一致,这里不再讲解,看一下 x ,y 轴同时具有初速度,即速度斜向的情况。
243 |
244 | 
245 |
246 | 改变 y 轴方向初速度:
247 |
248 | ```java
249 | // 默认y方向速度
250 | private float pointVY = 6;
251 | ```
252 |
253 | 在 updateBall 方法中增加对 y 方向的修改:
254 |
255 | ```java
256 | /**
257 | * 更新小球
258 | */
259 | private void updateBall() {
260 | //TODO --运动数据都由此函数变换
261 | mSpiderPoint.x += mSpiderPoint.vX;
262 | mSpiderPoint.y += mSpiderPoint.vY;
263 | if (mSpiderPoint.x > 400) {
264 | // 更改颜色
265 | mSpiderPoint.color = randomRGB();
266 | mSpiderPoint.vX = -mSpiderPoint.vX;
267 | }
268 | if (mSpiderPoint.x < -400) {
269 | mSpiderPoint.vX = -mSpiderPoint.vX;
270 | // 更改颜色
271 | mSpiderPoint.color = randomRGB();
272 | }
273 |
274 | if (mSpiderPoint.y > 400) {
275 | // 更改颜色
276 | mSpiderPoint.color = randomRGB();
277 | mSpiderPoint.vY = -mSpiderPoint.vY;
278 | }
279 | if (mSpiderPoint.y < -400) {
280 | mSpiderPoint.vY = -mSpiderPoint.vY;
281 | // 更改颜色
282 | mSpiderPoint.color = randomRGB();
283 | }
284 | }
285 | ```
286 |
287 | 效果如下图:
288 |
289 | 
290 |
291 | 蛛网「小点」并没有涉及到变速运动,有关变速运动可以链接以下地址进行查阅:
292 |
293 | [Android原生绘图之让你了解View的运动](https://juejin.im/post/5bee10376fb9a04a0e2cc4c2#heading-1)
294 |
295 | ### 构思代码
296 | 通过观察网页「蛛网」动态效果,可以细分为以下几点:
297 |
298 | - 绘制一定数量的小球(蛛网点)
299 |
300 | - 小球斜向运动(具有 x,y 轴方向速度),越界回弹
301 |
302 | - 遍历所有小球,若小球 A 与其他小球的距离小于一定值,则两小球连线,反之则不连线
303 |
304 | - 若小球 A 先与小球 B 连线,为了提高性能,防止过度绘制,小球 B 不再与小球 A 连线
305 |
306 | - 在手指触摸点绘制小球,同连线规则一致,连线其他小球,若手指移动,连线的所有小球向触摸点靠拢
307 |
308 | 接下来,具体看看代码该怎么写。
309 |
310 | ### 编写代码
311 |
312 | #### 起名字
313 |
314 | 取名是一门学问,好的名字能够让你记忆犹新,那就叫 **SpiderWebView** (蛛网控件)。
315 |
316 | #### 创建SpiderWebView
317 |
318 | 先是成员变量:
319 |
320 | ```java
321 | // 控件宽高
322 | private int mWidth;
323 | private int mHeight;
324 | // 画笔
325 | private Paint mPointPaint;
326 | private Paint mLinePaint;
327 | private Paint mTouchPaint;
328 | // 触摸点坐标
329 | private float mTouchX = -1;
330 | private float mTouchY = -1;
331 | // 数据源
332 | private List mSpiderPointList;
333 | // 相关参数配置
334 | private SpiderConfig mConfig;
335 | // 随机数
336 | private Random mRandom;
337 | // 手势帮助类 用于处理滚动与拖拽
338 | private GestureDetector mGestureDetector;
339 | ```
340 |
341 | 然后是构造函数:
342 |
343 | ```java
344 | // view 的默认构造函数 参数不做讲解
345 | public SpiderWebView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
346 | super(context, attrs, defStyleAttr);
347 | // setLayerType(LAYER_TYPE_HARDWARE, null);
348 | mSpiderPointList = new ArrayList<>();
349 | mConfig = new SpiderConfig();
350 | mRandom = new Random();
351 | mGestureDetector = new GestureDetector(context, mSimpleOnGestureListener);
352 | // 画笔初始化
353 | initPaint();
354 | }
355 | ```
356 |
357 | 接着按着「构思代码」中的效果逐一实现。
358 |
359 | #### 绘制一定数量的小球
360 |
361 | 指定数量为 50,每个小球的位置、颜色随机,并且具有不同的加速度。相关代码如下:
362 |
363 | ```java
364 | @Override
365 | protected void onSizeChanged(int w, int h, int oldw, int oldh) {
366 | super.onSizeChanged(w, h, oldw, oldh);
367 | mWidth = w;
368 | mHeight = h;
369 | }
370 | ```
371 |
372 | 先获取控件到控件的宽高。然后初始化小球集合:
373 |
374 | ```java
375 | /**
376 | * 初始化小点
377 | */
378 | private void initPoint() {
379 | for (int i = 0; i < mConfig.pointNum; i++) {
380 | int width = (int) (mRandom.nextFloat() * mWidth);
381 | int height = (int) (mRandom.nextFloat() * mHeight);
382 |
383 | SpiderPoint point = new SpiderPoint(width, height);
384 | int aX = 0;
385 | int aY = 0;
386 | // 获取加速度
387 | while (aX == 0) {
388 | aX = (int) ((mRandom.nextFloat() - 0.5F) * mConfig.pointAcceleration);
389 | }
390 | while (aY == 0) {
391 | aY = (int) ((mRandom.nextFloat() - 0.5F) * mConfig.pointAcceleration);
392 | }
393 | point.aX = aX;
394 | point.aY = aY;
395 | // 颜色随机
396 | point.color = randomRGB();
397 | mSpiderPointList.add(point);
398 | }
399 | }
400 | ```
401 |
402 | `mConfig` 表示配置参数,具体有以下成员变量:
403 |
404 | ```java
405 | public class SpiderConfig {
406 | // 小点半径 1
407 | public int pointRadius = DEFAULT_POINT_RADIUS;
408 | // 小点之间连线的粗细(宽度) 2
409 | public int lineWidth = DEFAULT_LINE_WIDTH;
410 | // 小点之间连线的透明度 150
411 | public int lineAlpha = DEFAULT_LINE_ALPHA;
412 | // 小点数量 50
413 | public int pointNum = DEFAULT_POINT_NUMBER;
414 | // 小点加速度 7
415 | public int pointAcceleration = DEFAULT_POINT_ACCELERATION;
416 | // 小点之间最长直线距离 280
417 | public int maxDistance = DEFAULT_MAX_DISTANCE;
418 | // 触摸点半径 1
419 | public int touchPointRadius = DEFAULT_TOUCH_POINT_RADIUS;
420 | // 引力大小 50
421 | public int gravitation_strength = DEFAULT_GRAVITATION_STRENGTH;
422 | }
423 | ```
424 |
425 | 获取到小球集合,最后绘制小球:
426 |
427 | ```java
428 | @Override
429 | protected void onDraw(Canvas canvas) {
430 | super.onDraw(canvas);
431 | // 绘制小球
432 | mPointPaint.setColor(spiderPoint.color);
433 | canvas.drawCircle(spiderPoint.x, spiderPoint.y, mConfig.pointRadius, mPointPaint);
434 | }
435 | ```
436 |
437 | 效果图如下:
438 |
439 | 
440 |
441 | #### 小球斜向运动,越界回弹
442 |
443 | 根据位移与速度公式 `位移 = 初位移 + 速度 * 时间` ,`速度 = 初速度 + 加速度` ,由于初速度为 0 ,时间为 1U,得到 `位移 = 初位移 + 加速度` :
444 |
445 | ```java
446 | spiderPoint.x += spiderPoint.aX;
447 | spiderPoint.y += spiderPoint.aY;
448 | ```
449 |
450 | 判定越界,原理在上文中已经提到:
451 |
452 | ```java
453 | @Override
454 | protected void onDraw(Canvas canvas) {
455 | super.onDraw(canvas);
456 | for (SpiderPoint spiderPoint : mSpiderPointList) {
457 |
458 | spiderPoint.x += spiderPoint.aX;
459 | spiderPoint.y += spiderPoint.aY;
460 |
461 | // 越界反弹
462 | if (spiderPoint.x <= mConfig.pointRadius) {
463 | spiderPoint.x = mConfig.pointRadius;
464 | spiderPoint.aX = -spiderPoint.aX;
465 | } else if (spiderPoint.x >= (mWidth - mConfig.pointRadius)) {
466 | spiderPoint.x = (mWidth - mConfig.pointRadius);
467 | spiderPoint.aX = -spiderPoint.aX;
468 | }
469 |
470 | if (spiderPoint.y <= mConfig.pointRadius) {
471 | spiderPoint.y = mConfig.pointRadius;
472 | spiderPoint.aY = -spiderPoint.aY;
473 | } else if (spiderPoint.y >= (mHeight - mConfig.pointRadius)) {
474 | spiderPoint.y = (mHeight - mConfig.pointRadius);
475 | spiderPoint.aY = -spiderPoint.aY;
476 | }
477 | }
478 | }
479 | ```
480 |
481 | 效果图如下:
482 |
483 | 
484 |
485 | #### 两球连线
486 |
487 | 循环遍历所有小球,若小球 A 与其他小球的距离小于一定值,则两小球连线,反之则不连线。双层遍历会导致一个问题,如果小球数量过多,双层遍历效率极低,从而引起界面卡顿,目前并没有找到更好的算法来解决这个问题,为了防止卡顿,对小球的数量有所控制,不能超过 150 个。
488 |
489 | ```java
490 | @Override
491 | protected void onDraw(Canvas canvas) {
492 | super.onDraw(canvas);
493 | for (SpiderPoint spiderPoint : mSpiderPointList) {
494 | // 绘制连线
495 | for (int i = 0; i < mSpiderPointList.size(); i++) {
496 | SpiderPoint point = mSpiderPointList.get(i);
497 | // 判定当前点与其他点之间的距离
498 | if (spiderPoint != point) {
499 | int distance = disPos2d(point.x, point.y, spiderPoint.x, spiderPoint.y);
500 | if (distance < mConfig.maxDistance) {
501 | // 绘制小点间的连线
502 | int alpha = (int) ((1.0F - (float) distance / mConfig.maxDistance) * mConfig.lineAlpha);
503 |
504 | mLinePaint.setColor(point.color);
505 | mLinePaint.setAlpha(alpha);
506 | canvas.drawLine(spiderPoint.x, spiderPoint.y, point.x, point.y, mLinePaint);
507 | }
508 | }
509 | }
510 | }
511 | invalidate();
512 | }
513 | ```
514 |
515 | `disPos2d` 方法用于计算两点之间的距离:
516 |
517 | ```java
518 | /**
519 | * 两点间距离函数
520 | */
521 | public static int disPos2d(float x1, float y1, float x2, float y2) {
522 | return (int) Math.sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2));
523 | }
524 | ```
525 |
526 | 如果两小球的距离在 `maxDistance` 范围内,距离越近透明度越小:
527 |
528 | ```java
529 | int alpha = (int) ((1.0F - (float) distance / mConfig.maxDistance) * mConfig.lineAlpha);
530 | ```
531 |
532 | 一起来看看两球连线的效果:
533 |
534 | 
535 |
536 | #### 防止过度绘制
537 |
538 | 由于双层遍历,若小球 A 先与小球 B 连线,为了提高性能,防止过度绘制,小球 B 不再与小球 A 连线。最开始的想法是记录小球 A 与其他小球的连线状态,当其他小球与小球 A 连线时,根据状态判定是否连线,如果小球 A 先与许多小球连线,必然会在小球 A 对象内部维护一个集合,用于存储小球 A 已经与哪些小球连线,这样效率并不高,反而把简单的问题变复杂了。最后用了一个取巧的办法:记录第一次循环的索引值,第二次循环从当前的索引值开始,这样就避免了两小球之间的多次连线。相关代码如下:
539 |
540 | ```java
541 | @Override
542 | protected void onDraw(Canvas canvas) {
543 | super.onDraw(canvas);
544 | int index = 0;
545 | for (SpiderPoint spiderPoint : mSpiderPointList) {
546 | // 绘制连线
547 | for (int i = index; i < mSpiderPointList.size(); i++) {
548 | SpiderPoint point = mSpiderPointList.get(i);
549 | // 判定当前点与其他点之间的距离
550 | if (spiderPoint != point) {
551 | int distance = disPos2d(point.x, point.y, spiderPoint.x, spiderPoint.y);
552 | if (distance < mConfig.maxDistance) {
553 | // 绘制小点间的连线
554 | int alpha = (int) ((1.0F - (float) distance / mConfig.maxDistance) * mConfig.lineAlpha);
555 |
556 | mLinePaint.setColor(point.color);
557 | mLinePaint.setAlpha(alpha);
558 | canvas.drawLine(spiderPoint.x, spiderPoint.y, point.x, point.y, mLinePaint);
559 | }
560 | }
561 | }
562 | index++;
563 | }
564 | invalidate();
565 | }
566 | ```
567 |
568 | #### 手势处理
569 |
570 | 还记得吗?在文章 [第一站小红书图片裁剪控件,深度解析大厂炫酷控件](https://blog.csdn.net/u012551350/article/details/87928720) 已经讲解了手势的处理流程。在网页版中触摸点(鼠标按下点)跟随鼠标移动而移动,在手机屏幕中「触摸点」(手指按下点)跟随手指移动而移动,从而需要重写手势类的 `onScroll` 方法:
571 |
572 | ```java
573 | @Override
574 | public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
575 | // 单根手指操作
576 | if (e1.getPointerCount() == e2.getPointerCount() && e1.getPointerCount() == 1) {
577 | mTouchX = e2.getX();
578 | mTouchY = e2.getY();
579 | return true;
580 | }
581 | return super.onScroll(e1, e2, distanceX, distanceY);
582 | }
583 | ```
584 |
585 | `onFling` 方法与 `onScroll` 方法处理方式一致,实时获取到「触摸点」位置。获取到了位置,绘制触摸点:
586 |
587 | ```java
588 | @Override
589 | protected void onDraw(Canvas canvas) {
590 | super.onDraw(canvas);
591 | // 绘制触摸点
592 | if (mTouchY != -1 && mTouchX != -1) {
593 | canvas.drawPoint(mTouchX, mTouchY, mTouchPaint);
594 | }
595 | }
596 | ```
597 |
598 | 若「触摸点」与其他小球的距离小于一定值,则两小球连线,反之则不连线:
599 |
600 | ```java
601 | @Override
602 | protected void onDraw(Canvas canvas) {
603 | super.onDraw(canvas);
604 | // 绘制触摸点与其他点的连线
605 | if (mTouchX != -1 && mTouchY != -1) {
606 | int offsetX = (int) (mTouchX - spiderPoint.x);
607 | int offsetY = (int) (mTouchY - spiderPoint.y);
608 | int distance = (int) Math.sqrt(offsetX * offsetX + offsetY * offsetY);
609 | if (distance < mConfig.maxDistance) {
610 | int alpha = (int) ((1.0F - (float) distance / mConfig.maxDistance) * mConfig.lineAlpha);
611 | mLinePaint.setColor(spiderPoint.color);
612 | mLinePaint.setAlpha(alpha);
613 | canvas.drawLine(spiderPoint.x, spiderPoint.y, mTouchX, mTouchY, mLinePaint);
614 | }
615 | }
616 | }
617 | ```
618 |
619 | 同时还具有与「触摸点」连线的所有小球向「触摸点」靠拢的效果,可采用「位移相对减少」的方案来实现靠拢的效果,相关代码如下:
620 |
621 | ```java
622 | @Override
623 | protected void onDraw(Canvas canvas) {
624 | super.onDraw(canvas);
625 | // 绘制触摸点与其他点的连线
626 | if (mTouchX != -1 && mTouchY != -1) {
627 | ....... // 省略相关代码
628 | if (distance < mConfig.maxDistance) {
629 | if (distance >= (mConfig.maxDistance - mConfig.gravitation_strength)) {
630 | // x 轴方向位移减少
631 | if (spiderPoint.x > mTouchX) {
632 | spiderPoint.x -= 0.03F * -offsetX;
633 | } else {
634 | spiderPoint.x += 0.03F * offsetX;
635 | }
636 | // y 轴方向位移减少
637 | if (spiderPoint.y > mTouchY) {
638 | spiderPoint.y -= 0.03F * -offsetY;
639 | } else {
640 | spiderPoint.y += 0.03F * offsetY;
641 | }
642 | }
643 | ....... // 省略相关代码
644 | ```
645 |
646 | 看看效果图:
647 |
648 | 
649 |
650 | 「五彩蛛网」控件差不多就讲到这里,有什么疑问,请留言讨论?
651 |
652 | ### 结束语
653 |
654 | 熬夜写的文章,有道不明的,还请多多包涵。同时也希望各位小伙伴都能过得都挺好。
655 |
656 | 源码如下:
657 |
658 | https://github.com/HpWens/MeiWidgetView
659 |
660 | https://github.com/HpWens/SpiderWebView
661 |
662 | 希望有志之士能够与我一起维护「控件人生」公众号。
663 |
664 |
665 |
666 | 扫一扫 关注我的公众号
667 |
668 | 想了解更多炫酷控件吗~
669 |
670 |
671 |
672 |
673 |
674 |
--------------------------------------------------------------------------------