├── 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 | 4 | 12 | -------------------------------------------------------------------------------- /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 | 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 | 17 | 27 | 28 | 29 | 30 | 31 | 32 | 34 | -------------------------------------------------------------------------------- /.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 15 | 16 | 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 | ![在这里插入图片描述](https://img-blog.csdnimg.cn/20190326164627600.gif)![在这里插入图片描述](https://img-blog.csdnimg.cn/20190326164634380.gif) 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 | ![在这里插入图片描述](https://img-blog.csdnimg.cn/20190326190110388.gif) 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 | ![在这里插入图片描述](https://img-blog.csdnimg.cn/20190326191356689.gif) 201 | 202 | 3、无限回弹,回弹变色 203 | 204 | ![在这里插入图片描述](https://img-blog.csdnimg.cn/20190326192213201.gif) 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 | ![图源网络,侵权必删](https://img-blog.csdnimg.cn/20190326192529502.png) 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 | ![在这里插入图片描述](https://img-blog.csdnimg.cn/2019032619422161.gif) 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 | ![在这里插入图片描述](https://img-blog.csdnimg.cn/2019032622532685.jpg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTI1NTEzNTA=,size_16,color_FFFFFF,t_70) 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 | ![在这里插入图片描述](https://img-blog.csdnimg.cn/20190326231354428.gif) 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 | ![在这里插入图片描述](https://img-blog.csdnimg.cn/20190326233236187.gif) 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 | ![在这里插入图片描述](https://img-blog.csdnimg.cn/201903270933057.gif) 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 | --------------------------------------------------------------------------------