├── app
├── .gitignore
├── src
│ ├── main
│ │ ├── res
│ │ │ ├── mipmap-hdpi
│ │ │ │ └── ic_launcher.png
│ │ │ ├── mipmap-mdpi
│ │ │ │ └── ic_launcher.png
│ │ │ ├── mipmap-xhdpi
│ │ │ │ └── ic_launcher.png
│ │ │ ├── mipmap-xxhdpi
│ │ │ │ └── ic_launcher.png
│ │ │ ├── mipmap-xxxhdpi
│ │ │ │ └── ic_launcher.png
│ │ │ ├── values
│ │ │ │ ├── strings.xml
│ │ │ │ ├── colors.xml
│ │ │ │ ├── dimens.xml
│ │ │ │ └── styles.xml
│ │ │ ├── values-v21
│ │ │ │ └── styles.xml
│ │ │ ├── values-w820dp
│ │ │ │ └── dimens.xml
│ │ │ ├── menu
│ │ │ │ └── menu_main.xml
│ │ │ └── layout
│ │ │ │ ├── content_main.xml
│ │ │ │ └── activity_main.xml
│ │ ├── AndroidManifest.xml
│ │ └── java
│ │ │ └── com
│ │ │ └── sam
│ │ │ └── minions
│ │ │ ├── MainActivity.java
│ │ │ └── MinionView.java
│ ├── test
│ │ └── java
│ │ │ └── com
│ │ │ └── sam
│ │ │ └── minions
│ │ │ └── ExampleUnitTest.java
│ └── androidTest
│ │ └── java
│ │ └── com
│ │ └── sam
│ │ └── minions
│ │ └── ApplicationTest.java
├── proguard-rules.pro
└── build.gradle
├── settings.gradle
├── .gitignore
├── gradle.properties
├── gradlew.bat
└── gradlew
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app'
2 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea/workspace.xml
5 | /.idea/libraries
6 | .DS_Store
7 | /build
8 | /captures
9 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/samwangds/minions/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/samwangds/minions/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/samwangds/minions/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/samwangds/minions/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/samwangds/minions/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | Minions
3 | Settings
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #3F51B5
4 | #303F9F
5 | #FF4081
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 16dp
4 | 16dp
5 | 16dp
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/values-v21/styles.xml:
--------------------------------------------------------------------------------
1 | >
2 |
3 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/values-w820dp/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 64dp
6 |
7 |
--------------------------------------------------------------------------------
/app/src/test/java/com/sam/minions/ExampleUnitTest.java:
--------------------------------------------------------------------------------
1 | package com.sam.minions;
2 |
3 | import org.junit.Test;
4 |
5 | import static org.junit.Assert.*;
6 |
7 | /**
8 | * To work on unit tests, switch the Test Artifact in the Build Variants view.
9 | */
10 | public class ExampleUnitTest {
11 | @Test
12 | public void addition_isCorrect() throws Exception {
13 | assertEquals(4, 2 + 2);
14 | }
15 | }
--------------------------------------------------------------------------------
/app/src/androidTest/java/com/sam/minions/ApplicationTest.java:
--------------------------------------------------------------------------------
1 | package com.sam.minions;
2 |
3 | import android.app.Application;
4 | import android.test.ApplicationTestCase;
5 |
6 | /**
7 | * Testing Fundamentals
8 | */
9 | public class ApplicationTest extends ApplicationTestCase {
10 | public ApplicationTest() {
11 | super(Application.class);
12 | }
13 | }
--------------------------------------------------------------------------------
/app/src/main/res/menu/menu_main.xml:
--------------------------------------------------------------------------------
1 |
11 |
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in D:\development\android_sdk/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | android {
4 | compileSdkVersion 23
5 | buildToolsVersion "23.0.1"
6 |
7 | defaultConfig {
8 | applicationId "com.sam.minions"
9 | minSdkVersion 21
10 | targetSdkVersion 23
11 | versionCode 1
12 | versionName "1.0"
13 | }
14 | buildTypes {
15 | release {
16 | minifyEnabled false
17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
18 | }
19 | }
20 | }
21 |
22 | dependencies {
23 | compile fileTree(dir: 'libs', include: ['*.jar'])
24 | testCompile 'junit:junit:4.12'
25 | compile 'com.android.support:appcompat-v7:23.1.1'
26 | compile 'com.android.support:design:23.1.1'
27 | }
28 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
11 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 |
3 | # IDE (e.g. Android Studio) users:
4 | # Gradle settings configured through the IDE *will override*
5 | # any settings specified in this file.
6 |
7 | # For more details on how to configure your build environment visit
8 | # http://www.gradle.org/docs/current/userguide/build_environment.html
9 |
10 | # Specifies the JVM arguments used for the daemon process.
11 | # The setting is particularly useful for tweaking memory settings.
12 | # Default value: -Xmx10248m -XX:MaxPermSize=256m
13 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
14 |
15 | # When configured, Gradle will run in incubating parallel mode.
16 | # This option should only be used with decoupled projects. More details, visit
17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
18 | # org.gradle.parallel=true
--------------------------------------------------------------------------------
/app/src/main/res/layout/content_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
15 |
16 |
20 |
21 |
27 |
28 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
15 |
16 |
22 |
23 |
24 |
25 |
26 |
27 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/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/sam/minions/MainActivity.java:
--------------------------------------------------------------------------------
1 | package com.sam.minions;
2 |
3 | import android.os.Bundle;
4 | import android.support.design.widget.FloatingActionButton;
5 | import android.support.v7.app.AppCompatActivity;
6 | import android.support.v7.widget.DefaultItemAnimator;
7 | import android.support.v7.widget.GridLayoutManager;
8 | import android.support.v7.widget.RecyclerView;
9 | import android.support.v7.widget.Toolbar;
10 | import android.view.View;
11 | import android.view.ViewGroup;
12 |
13 | public class MainActivity extends AppCompatActivity {
14 | private MinionView mMinionView;
15 | private RecyclerView mRecyclerView;
16 |
17 | @Override
18 | protected void onCreate(Bundle savedInstanceState) {
19 | super.onCreate(savedInstanceState);
20 | setContentView(R.layout.activity_main);
21 | Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
22 | setSupportActionBar(toolbar);
23 |
24 | FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
25 | fab.setOnClickListener(new View.OnClickListener() {
26 | @Override
27 | public void onClick(View view) {
28 | if (mMinionView.isShown()) {
29 | mMinionView.setVisibility(View.GONE);
30 | mRecyclerView.setVisibility(View.VISIBLE);
31 | } else {
32 | mMinionView.setVisibility(View.VISIBLE);
33 | mRecyclerView.setVisibility(View.GONE);
34 | }
35 | }
36 | });
37 |
38 |
39 | mMinionView = (MinionView) findViewById(R.id.minion);
40 |
41 | mRecyclerView = (RecyclerView) findViewById(R.id.recyclerview);
42 | mRecyclerView.setLayoutManager(new GridLayoutManager(this, 5));
43 | mRecyclerView.setItemAnimator(new DefaultItemAnimator());
44 | mRecyclerView.setAdapter(new MainAdapter());
45 | }
46 |
47 | private static class MainAdapter extends RecyclerView.Adapter {
48 |
49 | @Override
50 | public MinionsHolder onCreateViewHolder(ViewGroup parent, int viewType) {
51 | final MinionView minionView = new MinionView(parent.getContext());
52 | parent.addView(minionView);
53 | return new MinionsHolder(minionView);
54 | }
55 |
56 | @Override
57 | public void onBindViewHolder(MinionsHolder holder, int position) {
58 | holder.randomBodyColor();
59 | }
60 |
61 | @Override
62 | public int getItemCount() {
63 | return 100;
64 | }
65 | }
66 |
67 | private static class MinionsHolder extends RecyclerView.ViewHolder {
68 | private MinionView mMinionView;
69 |
70 | public MinionsHolder(View itemView) {
71 | super(itemView);
72 | mMinionView = (MinionView) itemView;
73 | }
74 |
75 | public void randomBodyColor() {
76 | mMinionView.randomBodyColor();
77 | }
78 | }
79 |
80 | }
81 |
--------------------------------------------------------------------------------
/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/java/com/sam/minions/MinionView.java:
--------------------------------------------------------------------------------
1 | package com.sam.minions;
2 |
3 | import android.annotation.TargetApi;
4 | import android.content.Context;
5 | import android.graphics.Canvas;
6 | import android.graphics.Color;
7 | import android.graphics.CornerPathEffect;
8 | import android.graphics.Paint;
9 | import android.graphics.Path;
10 | import android.graphics.RectF;
11 | import android.os.Build;
12 | import android.util.AttributeSet;
13 | import android.view.View;
14 |
15 | import java.util.Random;
16 |
17 | /**
18 | * @author SamWang(199004)
19 | * 2016/1/18 10:30
20 | */
21 | public class MinionView extends View {
22 |
23 | public MinionView(Context context) {
24 | super(context);
25 | }
26 |
27 | public MinionView(Context context, AttributeSet attrs) {
28 | super(context, attrs);
29 | }
30 |
31 | public MinionView(Context context, AttributeSet attrs, int defStyleAttr) {
32 | super(context, attrs, defStyleAttr);
33 | }
34 |
35 | @TargetApi(Build.VERSION_CODES.LOLLIPOP)
36 | public MinionView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
37 | super(context, attrs, defStyleAttr, defStyleRes);
38 | }
39 |
40 | @Override
41 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
42 | setMeasuredDimension(measure(widthMeasureSpec, true), measure(heightMeasureSpec, false));
43 | }
44 |
45 | private static final int DEFAULT_SIZE = 200; // View 默认大小
46 | private int widthForUnspecified;
47 | private int heightForUnspecified;
48 |
49 | /**
50 | * @param origin
51 | * @param isWidth 是否在测量宽,true 宽,false 高
52 | * @return
53 | */
54 | private int measure(int origin, boolean isWidth) {
55 | int result;
56 | int specMode = MeasureSpec.getMode(origin);
57 | int specSize = MeasureSpec.getSize(origin);
58 | switch (specMode) {
59 | case MeasureSpec.EXACTLY:
60 | case MeasureSpec.AT_MOST:
61 | result = specSize;
62 | if (isWidth) {
63 | widthForUnspecified = result;
64 | } else {
65 | heightForUnspecified = result;
66 | }
67 | break;
68 | case MeasureSpec.UNSPECIFIED:
69 | default:
70 | if (isWidth) {// 宽或高未指定的情况下,可以由另一端推算出来 - -. 如果两边都没指定就用默认值
71 | result = (int) (heightForUnspecified * BODY_WIDTH_HEIGHT_SCALE);
72 | } else {
73 | result = (int) (widthForUnspecified / BODY_WIDTH_HEIGHT_SCALE);
74 | }
75 | if (result == 0) {
76 | result = DEFAULT_SIZE;
77 | }
78 |
79 | break;
80 | }
81 |
82 | return result;
83 | }
84 |
85 | @Override
86 | protected void onSizeChanged(int w, int h, int oldw, int oldh) {
87 | super.onSizeChanged(w, h, oldw, oldh);
88 | initParams();
89 | }
90 |
91 | @Override
92 | protected void onDraw(Canvas canvas) {
93 | drawFeetShadow(canvas); // 脚下的阴影
94 | drawFeet(canvas); // 脚
95 | drawHands(canvas); // 手
96 | drawBody(canvas); // 身体
97 | drawClothes(canvas); // 衣服
98 | drawEyesMouth(canvas); // 眼睛,嘴巴
99 | drawBodyStroke(canvas); // 最后画身体的描边,可以摭住一些过渡的棱角
100 | }
101 |
102 | private Paint mPaint = new Paint();
103 | {
104 | mPaint.setAntiAlias(true);// 抗锯齿
105 | }
106 |
107 | private float bodyWidth;
108 | private float bodyHeight;
109 | private static final float BODY_SCALE = 0.6f; // 身体主干占整个view的比重
110 | private static final float BODY_WIDTH_HEIGHT_SCALE = 0.6f; // 身体的比例设定为 w:h = 3:5
111 |
112 | private float mStrokeWidth = 4; // 描边宽度
113 | private float offset; // 计算时,部分需要 考虑描边偏移
114 | private float radius; // 身体上下半圆的半径
115 | private int colorClothes = Color.rgb(32, 116, 160); // 衣服的颜色
116 | private int colorBody = Color.rgb(249, 217, 70); // 身体的颜色
117 | private int colorStroke = Color.BLACK;
118 | private RectF bodyRect = new RectF();
119 | private float handsHeight;// 计算出吊带的高度时,可以用来做手的高度
120 | private float footHeight; // 脚的高度,用来画脚部阴影时用
121 |
122 | private void initParams() {
123 | bodyWidth = Math.min(getWidth(), getHeight() * BODY_WIDTH_HEIGHT_SCALE) * BODY_SCALE;
124 | bodyHeight = Math.min(getWidth(), getHeight() * BODY_WIDTH_HEIGHT_SCALE) / BODY_WIDTH_HEIGHT_SCALE * BODY_SCALE;
125 |
126 | mStrokeWidth = Math.max(bodyWidth / 50, mStrokeWidth);
127 | offset = mStrokeWidth / 2;
128 |
129 | bodyRect.left = (getWidth() - bodyWidth) / 2;
130 | bodyRect.top = (getHeight() - bodyHeight) / 2;
131 | bodyRect.right = bodyRect.left + bodyWidth;
132 | bodyRect.bottom = bodyRect.top + bodyHeight;
133 |
134 | radius = bodyWidth / 2;
135 | footHeight = radius * 0.4333f;
136 |
137 | handsHeight = (getHeight() + bodyHeight) / 2 + offset - radius * 1.65f ;
138 | }
139 |
140 | private void drawBody(Canvas canvas) {
141 | mPaint.setColor(colorBody);
142 | mPaint.setStyle(Paint.Style.FILL);
143 |
144 | canvas.drawRoundRect(bodyRect, radius, radius, mPaint);
145 | }
146 |
147 | private void drawBodyStroke(Canvas canvas) {
148 | mPaint.setColor(colorStroke);
149 | mPaint.setStrokeWidth(mStrokeWidth);
150 | mPaint.setStyle(Paint.Style.STROKE);
151 | canvas.drawRoundRect(bodyRect, radius, radius, mPaint);
152 | }
153 |
154 | private RectF rect = new RectF();
155 | private float[] pts = new float[20];// 5 条线
156 | private Path path = new Path();
157 | private void drawClothes(Canvas canvas) {
158 |
159 | rect.left = (getWidth() - bodyWidth) / 2 + offset;
160 | rect.top = (getHeight() + bodyHeight) / 2 - radius * 2 + offset;
161 | rect.right = rect.left + bodyWidth - offset * 2;
162 | rect.bottom = rect.top + radius * 2 - offset * 2;
163 |
164 | mPaint.setColor(colorClothes);
165 | mPaint.setStyle(Paint.Style.FILL);
166 | mPaint.setStrokeWidth(mStrokeWidth);
167 | canvas.drawArc(rect, 0, 180, true, mPaint);
168 |
169 | int h = (int) (radius * 0.5);
170 | int w = (int) (radius * 0.3);
171 |
172 | rect.left += w;
173 | rect.top = rect.top + radius - h;
174 | rect.right -= w;
175 | rect.bottom = rect.top + h;
176 |
177 | canvas.drawRect(rect, mPaint);
178 |
179 | // 画横线,可优化:用 Path 来绘制,每个点用 rLineTo 去连接
180 | mPaint.setColor(colorStroke);
181 | mPaint.setStyle(Paint.Style.FILL);
182 | mPaint.setStrokeWidth(mStrokeWidth);
183 |
184 | pts[0] = rect.left - w;
185 | pts[1] = rect.top + h;
186 | pts[2] = pts[0] + w;
187 | pts[3] = pts[1];
188 |
189 | pts[4] = pts[2];
190 | pts[5] = pts[3] + offset;
191 | pts[6] = pts[4];
192 | pts[7] = pts[3] - h;
193 |
194 | pts[8] = pts[6] - offset;
195 | pts[9] = pts[7];
196 | pts[10] = pts[8] + (radius - w) * 2;
197 | pts[11] = pts[9];
198 |
199 | pts[12] = pts[10];
200 | pts[13] = pts[11] - offset;
201 | pts[14] = pts[12];
202 | pts[15] = pts[13] + h;
203 |
204 | pts[16] = pts[14] - offset;
205 | pts[17] = pts[15];
206 | pts[18] = pts[16] + w;
207 | pts[19] = pts[17];
208 | canvas.drawLines(pts, mPaint);
209 |
210 | // 画左吊带
211 | mPaint.setColor(colorClothes);
212 | mPaint.setStrokeWidth(mStrokeWidth);
213 | mPaint.setStyle(Paint.Style.FILL);
214 | path.reset();
215 | path.moveTo(rect.left - w - offset, handsHeight);
216 | path.lineTo(rect.left + h / 4f, rect.top + h / 2f);
217 | final float smallW = w / 2f * (float) Math.sin(Math.PI / 4);
218 | path.lineTo(rect.left + h / 4f + smallW, rect.top + h / 2f - smallW);
219 | final float smallW2 = w / (float) Math.sin(Math.PI / 4) / 2;
220 | path.lineTo(rect.left - w - offset, handsHeight - smallW2);
221 | canvas.drawPath(path, mPaint);
222 |
223 | mPaint.setColor(colorStroke);
224 | mPaint.setStrokeWidth(mStrokeWidth);
225 | mPaint.setStyle(Paint.Style.STROKE);
226 | canvas.drawPath(path, mPaint);
227 | mPaint.setStyle(Paint.Style.FILL_AND_STROKE);
228 | canvas.drawCircle(rect.left + h / 5f, rect.top + h / 4f, mStrokeWidth * 0.7f, mPaint);
229 |
230 | // 画右吊带,代码和左吊带差不多,坐标对称
231 | mPaint.setColor(colorClothes);
232 | mPaint.setStrokeWidth(mStrokeWidth);
233 | mPaint.setStyle(Paint.Style.FILL);
234 | path.reset();
235 | path.moveTo(rect.left - w + 2 * radius - offset, handsHeight);
236 | path.lineTo(rect.right - h / 4f, rect.top + h / 2f);
237 | path.lineTo(rect.right - h / 4f - smallW, rect.top + h / 2f - smallW);
238 | path.lineTo(rect.left - w + 2 * radius - offset, handsHeight- smallW2);
239 |
240 | canvas.drawPath(path, mPaint);
241 | mPaint.setColor(colorStroke);
242 | mPaint.setStrokeWidth(mStrokeWidth);
243 | mPaint.setStyle(Paint.Style.STROKE);
244 | canvas.drawPath(path, mPaint);
245 |
246 | mPaint.setStyle(Paint.Style.FILL_AND_STROKE);
247 | canvas.drawCircle(rect.right - h / 5f, rect.top + h / 4f, mStrokeWidth * 0.7f, mPaint);
248 |
249 | // 中间口袋
250 | mPaint.setColor(colorStroke);
251 | mPaint.setStrokeWidth(mStrokeWidth);
252 | mPaint.setStyle(Paint.Style.STROKE);
253 |
254 | path.reset();
255 | float radiusBigPocket = w / 2.0f;
256 | path.moveTo(rect.left + 1.5f * w, rect.bottom - h / 4f);
257 | path.lineTo(rect.right - 1.5f * w, rect.bottom - h / 4f);
258 | path.lineTo(rect.right - 1.5f * w, rect.bottom + h / 4f);
259 | path.addArc(rect.right - 1.5f * w - radiusBigPocket * 2, rect.bottom + h / 4f - radiusBigPocket,
260 | rect.right - 1.5f * w, rect.bottom + h / 4f + radiusBigPocket, 0, 90);
261 | path.lineTo(rect.left + 1.5f * w + radiusBigPocket, rect.bottom + h / 4f + radiusBigPocket);
262 |
263 | path.addArc(rect.left + 1.5f * w, rect.bottom + h / 4f - radiusBigPocket,
264 | rect.left + 1.5f * w + 2 * radiusBigPocket, rect.bottom + h / 4f + radiusBigPocket, 90, 90);
265 | path.lineTo(rect.left + 1.5f * w, rect.bottom - h / 4f - offset);
266 | canvas.drawPath(path, mPaint);
267 |
268 | // 下边一竖,分开裤子
269 | canvas.drawLine(bodyRect.left + bodyWidth / 2, bodyRect.bottom - h * 0.8f, bodyRect.left + bodyWidth / 2, bodyRect.bottom, mPaint);
270 | // 左边的小口袋
271 | float radiusSmallPocket = w * 1.2f;
272 | canvas.drawArc(bodyRect.left - radiusSmallPocket, bodyRect.bottom - radius - radiusSmallPocket,
273 | bodyRect.left + radiusSmallPocket, bodyRect.bottom - radius + radiusSmallPocket, 80, -60, false, mPaint);
274 | // 右边小口袋
275 | canvas.drawArc(bodyRect.right - radiusSmallPocket, bodyRect.bottom - radius - radiusSmallPocket,
276 | bodyRect.right + radiusSmallPocket, bodyRect.bottom - radius + radiusSmallPocket, 100, 60, false, mPaint);
277 | }
278 |
279 | private void drawEyesMouth(Canvas canvas) {
280 | // 眼睛中心处于上半圆直径 往上的高度偏移
281 | float eyesOffset = radius * 0.1f;
282 | mPaint.setStrokeWidth(mStrokeWidth * 5);
283 |
284 | // 计算眼镜带弧行的半径 分两段,以便眼睛中间有隔开的效果
285 | float radiusGlassesRibbon = (float) (radius / Math.sin(Math.PI / 20));
286 | rect.left = bodyRect.left + radius - radiusGlassesRibbon;
287 | rect.top = bodyRect.top + radius - (float) (radius / Math.tan(Math.PI / 20)) - radiusGlassesRibbon - eyesOffset;
288 | rect.right = rect.left + radiusGlassesRibbon * 2;
289 | rect.bottom = rect.top + radiusGlassesRibbon * 2;
290 | canvas.drawArc(rect, 81, 3, false, mPaint);
291 | canvas.drawArc(rect, 99, -3, false, mPaint);
292 |
293 | // 眼睛半径
294 | float radiusEyes = radius / 3;
295 | mPaint.setColor(Color.WHITE);
296 | mPaint.setStrokeWidth(mStrokeWidth);
297 | mPaint.setStyle(Paint.Style.FILL);
298 |
299 | canvas.drawCircle(bodyRect.left + bodyWidth / 2 - radiusEyes - offset, bodyRect.top + radius - eyesOffset, radiusEyes, mPaint);
300 | canvas.drawCircle(bodyRect.left + bodyWidth / 2 + radiusEyes + offset, bodyRect.top + radius - eyesOffset, radiusEyes, mPaint);
301 |
302 | mPaint.setColor(colorStroke);
303 | mPaint.setStyle(Paint.Style.STROKE);
304 | canvas.drawCircle(bodyRect.left + bodyWidth / 2 - radiusEyes - offset, bodyRect.top + radius - eyesOffset, radiusEyes, mPaint);
305 | canvas.drawCircle(bodyRect.left + bodyWidth / 2 + radiusEyes + offset, bodyRect.top + radius - eyesOffset, radiusEyes, mPaint);
306 |
307 | final float radiusEyeballBlack = radiusEyes / 3;
308 | mPaint.setStyle(Paint.Style.FILL);
309 | canvas.drawCircle(bodyRect.left + bodyWidth / 2 - radiusEyes - offset, bodyRect.top + radius - eyesOffset, radiusEyeballBlack, mPaint);
310 | canvas.drawCircle(bodyRect.left + bodyWidth / 2 + radiusEyes + offset, bodyRect.top + radius - eyesOffset, radiusEyeballBlack, mPaint);
311 |
312 | mPaint.setColor(Color.WHITE);
313 | final float radiusEyeballWhite = radiusEyeballBlack / 2;
314 | canvas.drawCircle(bodyRect.left + bodyWidth / 2 - radiusEyes + radiusEyeballWhite - offset * 2,
315 | bodyRect.top + radius - radiusEyeballWhite + offset - eyesOffset,
316 | radiusEyeballWhite, mPaint);
317 | canvas.drawCircle(bodyRect.left + bodyWidth / 2 + radiusEyes + radiusEyeballWhite,
318 | bodyRect.top + radius - radiusEyeballWhite + offset - eyesOffset,
319 | radiusEyeballWhite, mPaint);
320 |
321 | // 画嘴巴,因为位置和眼睛有相对关系,所以写在一块
322 | mPaint.setColor(colorStroke);
323 | mPaint.setStyle(Paint.Style.STROKE);
324 | mPaint.setStrokeWidth(mStrokeWidth);
325 | float radiusMonth = radius;
326 | rect.left = bodyRect.left;
327 | rect.top = bodyRect.top - radiusMonth / 2.5f;
328 | rect.right = rect.left + radiusMonth * 2;
329 | rect.bottom = rect.top + radiusMonth * 2;
330 | canvas.drawArc(rect, 95, -20, false, mPaint);
331 |
332 | }
333 |
334 |
335 | private void drawFeet(Canvas canvas) {
336 | mPaint.setStrokeWidth(mStrokeWidth);
337 | mPaint.setColor(colorStroke);
338 | mPaint.setStyle(Paint.Style.FILL_AND_STROKE);
339 |
340 | float radiusFoot = radius / 3 * 0.4f;
341 | float leftFootStartX = bodyRect.left + radius - offset * 2;
342 | float leftFootStartY = bodyRect.bottom - offset;
343 | float footWidthA = radius * 0.5f;//脚宽度大-到半圆结束
344 | float footWidthB = footWidthA / 3;//脚宽度-比较细的部分
345 |
346 | // 左脚
347 | path.reset();
348 | path.moveTo(leftFootStartX, leftFootStartY);
349 | path.lineTo(leftFootStartX, leftFootStartY + footHeight);
350 | path.lineTo(leftFootStartX - footWidthA + radiusFoot, leftFootStartY + footHeight);
351 |
352 | rect.left = leftFootStartX - footWidthA;
353 | rect.top = leftFootStartY + footHeight - radiusFoot * 2;
354 | rect.right = rect.left + radiusFoot * 2;
355 | rect.bottom = rect.top + radiusFoot * 2;
356 | path.addArc(rect, 90, 180);
357 | path.lineTo(rect.left + radiusFoot + footWidthB, rect.top);
358 | path.lineTo(rect.left + radiusFoot + footWidthB, leftFootStartY);
359 | path.lineTo(leftFootStartX, leftFootStartY);
360 | canvas.drawPath(path, mPaint);
361 |
362 | // 右脚
363 | float rightFootStartX = bodyRect.left + radius + offset * 2;
364 | float rightFootStartY = leftFootStartY;
365 | path.reset();
366 | path.moveTo(rightFootStartX, rightFootStartY);
367 | path.lineTo(rightFootStartX, rightFootStartY + footHeight);
368 | path.lineTo(rightFootStartX + footWidthA - radiusFoot, rightFootStartY + footHeight);
369 |
370 | rect.left = rightFootStartX + footWidthA - radiusFoot * 2;
371 | rect.top = rightFootStartY + footHeight - radiusFoot * 2;
372 | rect.right = rect.left + radiusFoot * 2;
373 | rect.bottom = rect.top + radiusFoot * 2;
374 | path.addArc(rect, 90, -180);
375 | path.lineTo(rect.right - radiusFoot - footWidthB, rect.top);
376 | path.lineTo(rect.right - radiusFoot - footWidthB, rightFootStartY);
377 | path.lineTo(rightFootStartX, rightFootStartY);
378 | canvas.drawPath(path, mPaint);
379 | }
380 |
381 | private void drawFeetShadow(Canvas canvas) {
382 | mPaint.setColor(getResources().getColor(android.R.color.darker_gray));
383 | canvas.drawOval(bodyRect.left + bodyWidth * 0.15f,
384 | bodyRect.bottom - offset + footHeight,
385 | bodyRect.right - bodyWidth * 0.15f,
386 | bodyRect.bottom - offset + footHeight + mStrokeWidth * 1.3f, mPaint);
387 | }
388 |
389 | private void drawHands(Canvas canvas) {
390 | mPaint.setStrokeWidth(mStrokeWidth);
391 | mPaint.setStyle(Paint.Style.FILL_AND_STROKE);
392 | mPaint.setColor(colorBody);
393 |
394 | // 左手
395 | path.reset();
396 | float hypotenuse = bodyRect.bottom - radius - handsHeight;
397 | float radiusHand = hypotenuse / 6;
398 | mPaint.setPathEffect(new CornerPathEffect(radiusHand));
399 |
400 | path.moveTo(bodyRect.left, handsHeight);
401 | path.lineTo(bodyRect.left - hypotenuse / 2, handsHeight + hypotenuse / 2);
402 | path.lineTo(bodyRect.left +offset, bodyRect.bottom - radius +offset);
403 | path.lineTo(bodyRect.left, handsHeight);//增加兼容性,path没闭合在一起机子上会使手的下面的点没办法与裤子重合
404 | canvas.drawPath(path, mPaint);
405 |
406 | mPaint.setStrokeWidth(mStrokeWidth);
407 | mPaint.setStyle(Paint.Style.STROKE);
408 | mPaint.setColor(colorStroke);
409 | canvas.drawPath(path, mPaint);
410 |
411 | // 右手
412 | path.reset();
413 | mPaint.setStyle(Paint.Style.FILL);
414 | mPaint.setColor(colorBody);
415 |
416 | path.moveTo(bodyRect.right, handsHeight);
417 | path.lineTo(bodyRect.right + hypotenuse / 2, handsHeight + hypotenuse / 2);
418 | path.lineTo(bodyRect.right -offset, bodyRect.bottom - radius +offset);
419 | path.lineTo(bodyRect.right, handsHeight);
420 | canvas.drawPath(path, mPaint);
421 |
422 | mPaint.setStrokeWidth(mStrokeWidth);
423 | mPaint.setStyle(Paint.Style.STROKE);
424 | mPaint.setColor(colorStroke);
425 | canvas.drawPath(path, mPaint);
426 |
427 | // 一个慢动作 - -||| 手臂内侧拐点
428 | path.reset();
429 | mPaint.setStyle(Paint.Style.FILL);
430 | path.moveTo(bodyRect.left, handsHeight + hypotenuse / 2 - mStrokeWidth);
431 | path.lineTo(bodyRect.left - mStrokeWidth * 2, handsHeight + hypotenuse / 2 + mStrokeWidth * 2);
432 | path.lineTo(bodyRect.left, handsHeight + hypotenuse / 2 + mStrokeWidth);
433 | path.lineTo(bodyRect.left, handsHeight + hypotenuse / 2 - mStrokeWidth);
434 | canvas.drawPath(path, mPaint);
435 |
436 | path.reset();
437 | path.moveTo(bodyRect.right, handsHeight + hypotenuse / 2 - mStrokeWidth);
438 | path.lineTo(bodyRect.right + mStrokeWidth * 2, handsHeight + hypotenuse / 2 + mStrokeWidth * 2);
439 | path.lineTo(bodyRect.right, handsHeight + hypotenuse / 2 + mStrokeWidth);
440 | path.lineTo(bodyRect.right, handsHeight + hypotenuse / 2 - mStrokeWidth);
441 | canvas.drawPath(path, mPaint);
442 |
443 | mPaint.setPathEffect(null); //避免影响其它绘制
444 | }
445 |
446 | public void randomBodyColor() {
447 | Random random = new Random();
448 | colorBody = Color.rgb(random.nextInt(255), random.nextInt(255), random.nextInt(255));
449 | invalidate();
450 | }
451 | }
452 |
--------------------------------------------------------------------------------