├── .gitignore
├── README.md
├── app
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── com
│ │ └── dreamlive
│ │ └── hotimgproject
│ │ └── ExampleInstrumentationTest.java
│ ├── main
│ ├── AndroidManifest.xml
│ ├── assets
│ │ ├── china.png
│ │ └── china.xml
│ ├── java
│ │ └── com
│ │ │ └── dreamlive
│ │ │ └── hotimgproject
│ │ │ └── MainActivity.java
│ └── res
│ │ ├── layout
│ │ └── activity_main.xml
│ │ ├── mipmap-hdpi
│ │ └── ic_launcher.png
│ │ ├── mipmap-mdpi
│ │ └── ic_launcher.png
│ │ ├── mipmap-xhdpi
│ │ └── ic_launcher.png
│ │ ├── mipmap-xxhdpi
│ │ └── ic_launcher.png
│ │ ├── mipmap-xxxhdpi
│ │ └── ic_launcher.png
│ │ ├── values-w820dp
│ │ └── dimens.xml
│ │ └── values
│ │ ├── colors.xml
│ │ ├── dimens.xml
│ │ ├── strings.xml
│ │ └── styles.xml
│ └── test
│ └── java
│ └── com
│ └── dreamlive
│ └── hotimgproject
│ └── ExampleUnitTest.java
├── build.gradle
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── hotimglibrary
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── com
│ │ └── dreamlive
│ │ └── hotimglibrary
│ │ └── ExampleInstrumentationTest.java
│ ├── main
│ ├── AndroidManifest.xml
│ ├── java
│ │ └── com
│ │ │ └── dreamlive
│ │ │ └── hotimglibrary
│ │ │ ├── entity
│ │ │ └── HotArea.java
│ │ │ ├── utils
│ │ │ ├── FileUtils.java
│ │ │ ├── LogUtils.java
│ │ │ └── XMLUtils.java
│ │ │ └── view
│ │ │ └── HotClickView.java
│ └── res
│ │ └── values
│ │ ├── dimens.xml
│ │ └── strings.xml
│ └── test
│ └── java
│ └── com
│ └── dreamlive
│ └── hotimglibrary
│ └── ExampleUnitTest.java
├── images
└── xiaoguo.gif
└── settings.gradle
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea/workspace.xml
5 | /.idea/libraries
6 | .DS_Store
7 | /build
8 | /captures
9 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # HotImg
2 | 图片不规则区域点击事件处理
3 | # HotImg是基于对一张图片进行不规则区域点击响应不同事件。
4 | 
5 |
6 | # 基本内容
7 | 1.对图片的部分区域进行可响应事件
8 | 2.对图片进行缩放后,仍可响应区域点击事件
9 | 3.对图片进行移动后.仍可响应区域点击事件
10 | 4.当图片移出边界区域后,可进行回弹.
11 |
12 | # 如何使用
13 | 1、 xml配置
14 | ```java
15 |
20 | ```
21 | 2、 禁止缩放
22 |
23 | ```java
24 | mHotView.setCanScale(false);
25 | ```
26 | 3、 禁止滑动
27 |
28 | ```java
29 | mHotView.setCanMove(false);
30 | ```
31 | 4、 设置监听事件
32 | ```java
33 | mHotView.setOnClickListener(new HotClickView.OnClickListener() {
34 | @Override
35 | public void OnClick(View view, HotArea hotArea) {
36 | Toast.makeText(MainActivity.this, "你点击了" + hotArea.getDesc(), Toast.LENGTH_SHORT).show();
37 | }
38 | });
39 | ```
40 | 5、 图片点击区域的设置
41 | 将图片中每个点击区域的像素坐标点配置assets文件下的xml中,例如一张图片像素800X600,在其坐上方(从左上方顶点开始)有一个50x50的正方形点击区域,那么他们pts为{0,0,0,50,50,50,50,0}也就是正方形的四个区域。
42 |
43 | # Thanks
44 | Everyone who has contributed code and reported issues and pull requests!
45 |
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | android {
4 | compileSdkVersion 23
5 | buildToolsVersion "23.0.3"
6 | defaultConfig {
7 | applicationId "com.dreamlive.hotimgproject"
8 | minSdkVersion 15
9 | targetSdkVersion 23
10 | versionCode 1
11 | versionName "1.0"
12 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
13 | }
14 | buildTypes {
15 | release {
16 | minifyEnabled false
17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
18 | }
19 | }
20 | }
21 |
22 | dependencies {
23 | compile fileTree(include: ['*.jar'], dir: 'libs')
24 | compile 'com.android.support:appcompat-v7:23.3.0'
25 | compile project(':hotimglibrary')
26 | }
27 |
--------------------------------------------------------------------------------
/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 F:\mengweiData\android-sdk-windows/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/com/dreamlive/hotimgproject/ExampleInstrumentationTest.java:
--------------------------------------------------------------------------------
1 | package com.dreamlive.hotimgproject;
2 |
3 | import android.content.Context;
4 | import android.support.test.InstrumentationRegistry;
5 | import android.support.test.filters.MediumTest;
6 | import android.support.test.runner.AndroidJUnit4;
7 |
8 | import org.junit.Test;
9 | import org.junit.runner.RunWith;
10 |
11 |
12 | import static org.junit.Assert.*;
13 |
14 | /**
15 | * Instrumentation test, which will execute on an Android device.
16 | *
17 | * @see Testing documentation
18 | */
19 | @MediumTest
20 | @RunWith(AndroidJUnit4.class)
21 | public class ExampleInstrumentationTest {
22 | @Test
23 | public void useAppContext() throws Exception {
24 | // Context of the app under test.
25 | Context appContext = InstrumentationRegistry.getTargetContext();
26 |
27 | assertEquals("com.dreamlive.hotimgproject", appContext.getPackageName());
28 | }
29 | }
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/app/src/main/assets/china.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dreamlivemeng/HotImg/03a4033c788b87bb7ded7be7a029524a9324f39e/app/src/main/assets/china.png
--------------------------------------------------------------------------------
/app/src/main/assets/china.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
11 |
12 |
--------------------------------------------------------------------------------
/app/src/main/java/com/dreamlive/hotimgproject/MainActivity.java:
--------------------------------------------------------------------------------
1 | package com.dreamlive.hotimgproject;
2 |
3 | import android.content.res.AssetManager;
4 | import android.os.Bundle;
5 | import android.support.v7.app.AppCompatActivity;
6 | import android.view.View;
7 | import android.widget.Toast;
8 |
9 | import com.dreamlive.hotimglibrary.entity.HotArea;
10 | import com.dreamlive.hotimglibrary.utils.FileUtils;
11 | import com.dreamlive.hotimglibrary.view.HotClickView;
12 |
13 | import java.io.InputStream;
14 |
15 | public class MainActivity extends AppCompatActivity implements HotClickView.OnClickListener {
16 |
17 | private HotClickView mHotView;
18 |
19 | @Override
20 | protected void onCreate(Bundle savedInstanceState) {
21 | super.onCreate(savedInstanceState);
22 | setContentView(R.layout.activity_main);
23 | initParam();
24 | initDatas();
25 | }
26 |
27 | private void initParam() {
28 | mHotView = (HotClickView) findViewById(R.id.a_main_hotview);
29 | // mHotView.setCanMove(false);
30 | // mHotView.setCanScale(false);
31 | }
32 |
33 | protected void initDatas() {
34 | AssetManager assetManager = getResources().getAssets();
35 | InputStream imgInputStream = null;
36 | InputStream fileInputStream = null;
37 | try {
38 | imgInputStream = assetManager.open("china.png");
39 | fileInputStream = assetManager.open("china.xml");
40 | mHotView.setImageBitmap(fileInputStream, imgInputStream, HotClickView.FIT_XY);
41 | mHotView.setOnClickListener(this);
42 | } catch (Exception e) {
43 | e.printStackTrace();
44 | } finally {
45 | FileUtils.closeInputStream(imgInputStream);
46 | FileUtils.closeInputStream(fileInputStream);
47 | }
48 | }
49 |
50 |
51 | @Override
52 | public void OnClick(View view, HotArea hotArea) {
53 | Toast.makeText(MainActivity.this, "你点击了" + hotArea.getDesc(), Toast.LENGTH_SHORT).show();
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
11 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dreamlivemeng/HotImg/03a4033c788b87bb7ded7be7a029524a9324f39e/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dreamlivemeng/HotImg/03a4033c788b87bb7ded7be7a029524a9324f39e/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dreamlivemeng/HotImg/03a4033c788b87bb7ded7be7a029524a9324f39e/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dreamlivemeng/HotImg/03a4033c788b87bb7ded7be7a029524a9324f39e/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dreamlivemeng/HotImg/03a4033c788b87bb7ded7be7a029524a9324f39e/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/values-w820dp/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 64dp
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #3F51B5
4 | #303F9F
5 | #FF4081
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 16dp
4 | 16dp
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | HotImgProject
3 |
4 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/app/src/test/java/com/dreamlive/hotimgproject/ExampleUnitTest.java:
--------------------------------------------------------------------------------
1 | package com.dreamlive.hotimgproject;
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() throws Exception {
15 | assertEquals(4, 2 + 2);
16 | }
17 | }
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 |
3 | buildscript {
4 | repositories {
5 | jcenter()
6 | }
7 | dependencies {
8 | classpath 'com.android.tools.build:gradle:2.2.0-alpha1'
9 |
10 | // NOTE: Do not place your application dependencies here; they belong
11 | // in the individual module build.gradle files
12 | }
13 | }
14 |
15 | allprojects {
16 | repositories {
17 | jcenter()
18 | }
19 | }
20 |
21 | task clean(type: Delete) {
22 | delete rootProject.buildDir
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 | org.gradle.jvmargs=-Xmx1536m
13 |
14 | # When configured, Gradle will run in incubating parallel mode.
15 | # This option should only be used with decoupled projects. More details, visit
16 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
17 | # org.gradle.parallel=true
18 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dreamlivemeng/HotImg/03a4033c788b87bb7ded7be7a029524a9324f39e/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Mon Dec 28 10:00:20 PST 2015
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-2.10-all.zip
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # Attempt to set APP_HOME
46 | # Resolve links: $0 may be a link
47 | PRG="$0"
48 | # Need this for relative symlinks.
49 | while [ -h "$PRG" ] ; do
50 | ls=`ls -ld "$PRG"`
51 | link=`expr "$ls" : '.*-> \(.*\)$'`
52 | if expr "$link" : '/.*' > /dev/null; then
53 | PRG="$link"
54 | else
55 | PRG=`dirname "$PRG"`"/$link"
56 | fi
57 | done
58 | SAVED="`pwd`"
59 | cd "`dirname \"$PRG\"`/" >/dev/null
60 | APP_HOME="`pwd -P`"
61 | cd "$SAVED" >/dev/null
62 |
63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
64 |
65 | # Determine the Java command to use to start the JVM.
66 | if [ -n "$JAVA_HOME" ] ; then
67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
68 | # IBM's JDK on AIX uses strange locations for the executables
69 | JAVACMD="$JAVA_HOME/jre/sh/java"
70 | else
71 | JAVACMD="$JAVA_HOME/bin/java"
72 | fi
73 | if [ ! -x "$JAVACMD" ] ; then
74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
75 |
76 | Please set the JAVA_HOME variable in your environment to match the
77 | location of your Java installation."
78 | fi
79 | else
80 | JAVACMD="java"
81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
82 |
83 | Please set the JAVA_HOME variable in your environment to match the
84 | location of your Java installation."
85 | fi
86 |
87 | # Increase the maximum file descriptors if we can.
88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
89 | MAX_FD_LIMIT=`ulimit -H -n`
90 | if [ $? -eq 0 ] ; then
91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
92 | MAX_FD="$MAX_FD_LIMIT"
93 | fi
94 | ulimit -n $MAX_FD
95 | if [ $? -ne 0 ] ; then
96 | warn "Could not set maximum file descriptor limit: $MAX_FD"
97 | fi
98 | else
99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
100 | fi
101 | fi
102 |
103 | # For Darwin, add options to specify how the application appears in the dock
104 | if $darwin; then
105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
106 | fi
107 |
108 | # For Cygwin, switch paths to Windows format before running java
109 | if $cygwin ; then
110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
112 | JAVACMD=`cygpath --unix "$JAVACMD"`
113 |
114 | # We build the pattern for arguments to be converted via cygpath
115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
116 | SEP=""
117 | for dir in $ROOTDIRSRAW ; do
118 | ROOTDIRS="$ROOTDIRS$SEP$dir"
119 | SEP="|"
120 | done
121 | OURCYGPATTERN="(^($ROOTDIRS))"
122 | # Add a user-defined pattern to the cygpath arguments
123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
125 | fi
126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
127 | i=0
128 | for arg in "$@" ; do
129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
131 |
132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
134 | else
135 | eval `echo args$i`="\"$arg\""
136 | fi
137 | i=$((i+1))
138 | done
139 | case $i in
140 | (0) set -- ;;
141 | (1) set -- "$args0" ;;
142 | (2) set -- "$args0" "$args1" ;;
143 | (3) set -- "$args0" "$args1" "$args2" ;;
144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
150 | esac
151 | fi
152 |
153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
154 | function splitJvmOpts() {
155 | JVM_OPTS=("$@")
156 | }
157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
159 |
160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
161 |
--------------------------------------------------------------------------------
/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 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
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 Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/hotimglibrary/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/hotimglibrary/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 |
3 | android {
4 | compileSdkVersion 23
5 | buildToolsVersion "23.0.3"
6 |
7 | defaultConfig {
8 | minSdkVersion 15
9 | targetSdkVersion 23
10 | versionCode 1
11 | versionName "1.0"
12 |
13 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
14 | }
15 | buildTypes {
16 | release {
17 | minifyEnabled false
18 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
19 | }
20 | }
21 | }
22 |
23 | dependencies {
24 | compile fileTree(dir: 'libs', include: ['*.jar'])
25 | }
26 |
--------------------------------------------------------------------------------
/hotimglibrary/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 F:\mengweiData\android-sdk-windows/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 |
--------------------------------------------------------------------------------
/hotimglibrary/src/androidTest/java/com/dreamlive/hotimglibrary/ExampleInstrumentationTest.java:
--------------------------------------------------------------------------------
1 | package com.dreamlive.hotimglibrary;
2 |
3 | import android.content.Context;
4 | import android.support.test.InstrumentationRegistry;
5 | import android.support.test.filters.MediumTest;
6 | import android.support.test.runner.AndroidJUnit4;
7 |
8 | import org.junit.Test;
9 | import org.junit.runner.RunWith;
10 |
11 |
12 | import static org.junit.Assert.*;
13 |
14 | /**
15 | * Instrumentation test, which will execute on an Android device.
16 | *
17 | * @see Testing documentation
18 | */
19 | @MediumTest
20 | @RunWith(AndroidJUnit4.class)
21 | public class ExampleInstrumentationTest {
22 | @Test
23 | public void useAppContext() throws Exception {
24 | // Context of the app under test.
25 | Context appContext = InstrumentationRegistry.getTargetContext();
26 |
27 | assertEquals("com.dreamlive.hotimglibrary.test", appContext.getPackageName());
28 | }
29 | }
--------------------------------------------------------------------------------
/hotimglibrary/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/hotimglibrary/src/main/java/com/dreamlive/hotimglibrary/entity/HotArea.java:
--------------------------------------------------------------------------------
1 | package com.dreamlive.hotimglibrary.entity;
2 |
3 | import android.graphics.Path;
4 | import android.graphics.RectF;
5 | import android.graphics.Region;
6 | import android.text.TextUtils;
7 | import android.util.Log;
8 |
9 | import java.io.Serializable;
10 | import java.util.ArrayList;
11 | import java.util.List;
12 |
13 | /**
14 | * 图片点击区域对应的实体类
15 | * Created by dreamlivemeng on 2016/6/7.
16 | */
17 | public class HotArea implements Serializable {
18 | private static final long serialVersionUID = -2129399099105307178L;
19 | /**
20 | * 区域id
21 | */
22 | private String areaId;
23 | /**
24 | * 区域名称
25 | */
26 | private String areaTitle;
27 | /**
28 | * 区域介绍
29 | */
30 | private String desc;
31 | /***
32 | * 区域点坐标
33 | */
34 | private int[] pts;
35 | private List areas = new ArrayList();
36 |
37 | public HotArea() {
38 | }
39 |
40 | public HotArea(String areaId, String areaTitle, String desc, int[] pts) {
41 | this.areaId = areaId;
42 | this.areaTitle = areaTitle;
43 | this.desc = desc;
44 | this.pts = pts;
45 | }
46 |
47 | public HotArea(String areaId, String areaTitle, String desc, String[] pts) {
48 | this.areaId = areaId;
49 | this.areaTitle = areaTitle;
50 | this.desc = desc;
51 | setStrArrayToIntArray(pts);
52 | }
53 |
54 | public String getAreaId() {
55 | return areaId;
56 | }
57 |
58 | public void setAreaId(String areaId) {
59 | this.areaId = areaId;
60 | }
61 |
62 | public String getAreaTitle() {
63 | return areaTitle;
64 | }
65 |
66 | public void setAreaTitle(String areaTitle) {
67 | this.areaTitle = areaTitle;
68 | }
69 |
70 | public String getDesc() {
71 | return desc;
72 | }
73 |
74 | public void setDesc(String desc) {
75 | this.desc = desc;
76 | }
77 |
78 | public int[] getPts() {
79 | return pts;
80 | }
81 |
82 | public void setPts(int[] pts) {
83 | this.pts = pts;
84 | }
85 |
86 | public List getAreas() {
87 | return areas;
88 | }
89 |
90 | public void setAreas(List areas) {
91 | this.areas = areas;
92 | }
93 |
94 | public CheckArea getCheckArea() {
95 | CheckArea checkArea = null;
96 | if (pts != null) {
97 | checkArea = new CheckArea(pts);
98 | }
99 | return checkArea;
100 | }
101 |
102 | public void setPts(String[] pts) {
103 | setStrArrayToIntArray(pts);
104 | }
105 |
106 | private void setStrArrayToIntArray(String[] pts) {
107 | if (null != pts && 0 != pts.length) {
108 | int len = pts.length;
109 | this.pts = new int[len];
110 | for (int i = 0; i < len; ++i) {
111 | try {
112 | this.pts[i] = Integer.parseInt(pts[i]);
113 | } catch (Exception e) {
114 | this.pts[i] = 0;
115 | Log.e("SmartPit", e.getMessage());
116 | }
117 | }
118 | }
119 | }
120 |
121 | public void setPts(String pts, String split) {
122 | if (!TextUtils.isEmpty(pts) && !TextUtils.isEmpty(split)) {
123 | String[] points = pts.split(split);
124 | setPts(points);
125 | }
126 | }
127 |
128 | /**
129 | * 写内部类的原因在于在Activity之间传递时,
130 | * 不能传未实现Serializable接口的类
131 | */
132 | public class CheckArea {
133 | private final Path path;
134 | //当前处理是从点的个数来判断是矩形 还是多边形,这两种的方式对点的位置判断不太一样
135 | private final boolean isRectF;
136 |
137 | private CheckArea(int[] pts) {
138 | this.path = new Path();
139 | int len = pts.length;
140 | isRectF = len == 4;
141 | Log.e("TAG", "len================" + len);
142 | for (int i = 0; i < len; ) {
143 | if (i == 0) {
144 | this.path.moveTo(pts[i++], pts[i++]);
145 | } else {
146 | this.path.lineTo(pts[i++], pts[i++]);
147 | }
148 | }
149 | this.path.close();
150 | }
151 |
152 | public Path getPath() {
153 | return this.path;
154 | }
155 |
156 | /**
157 | * 检测是否在区域范围内
158 | *
159 | * @param rectf 从外部传可以重用
160 | * @param x
161 | * @param y
162 | * @return
163 | */
164 | public boolean isInArea(RectF rectf, float x, float y) {
165 | boolean resStatus = false;
166 | if (this.path != null) {
167 | rectf.setEmpty();
168 | path.computeBounds(rectf, true);
169 | if (isRectF) {
170 | //当是矩形时
171 | resStatus = rectf.contains(x, y);
172 | } else {
173 | //如果是多边形时
174 | Region region = new Region();
175 | region.setPath(path, region);
176 | region.setPath(path, new Region((int) rectf.left, (int) rectf.top, (int) rectf.right, (int) rectf.bottom));
177 | resStatus = region.contains((int) x, (int) y);
178 | }
179 | }
180 | return resStatus;
181 | }
182 | }
183 | }
184 |
--------------------------------------------------------------------------------
/hotimglibrary/src/main/java/com/dreamlive/hotimglibrary/utils/FileUtils.java:
--------------------------------------------------------------------------------
1 | package com.dreamlive.hotimglibrary.utils;
2 |
3 | import java.io.IOException;
4 | import java.io.InputStream;
5 |
6 | /**
7 | * 文件操作帮助类
8 | * Created by dreamlivemeng on 2016/6/7.
9 | */
10 | public final class FileUtils {
11 |
12 | private FileUtils() throws InstantiationException {
13 | throw new InstantiationException("This utility class is not created for instantiation");
14 | }
15 |
16 | /**
17 | * 关闭输入流
18 | *
19 | * @param is
20 | */
21 | public static void closeInputStream(InputStream is) {
22 | if (is != null) {
23 | try {
24 | is.close();
25 | } catch (IOException e) {
26 | e.printStackTrace();
27 | }
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/hotimglibrary/src/main/java/com/dreamlive/hotimglibrary/utils/LogUtils.java:
--------------------------------------------------------------------------------
1 | package com.dreamlive.hotimglibrary.utils;
2 |
3 | import android.text.TextUtils;
4 | import android.util.Log;
5 |
6 | /**
7 | * 日志打印帮助类
8 | * Created by dreamlivemeng on 2016/6/7.
9 | */
10 | public final class LogUtils {
11 |
12 | private final static boolean IS_DEBUG = true;
13 |
14 | private LogUtils() throws InstantiationException {
15 | throw new InstantiationException("This utility class is not created for instantiation");
16 | }
17 |
18 | public static void e(String tag, String msg) {
19 | if(IS_DEBUG) {
20 | Log.e(tag, checkMsg(msg));
21 | }
22 | }
23 |
24 | public static void i(String tag, String msg) {
25 | if(IS_DEBUG) {
26 | Log.i(tag, checkMsg(msg));
27 | }
28 | }
29 |
30 | public static void d(String tag, String msg) {
31 | if(IS_DEBUG) {
32 | Log.d(tag, checkMsg(msg));
33 | }
34 | }
35 |
36 | public static void e(String tag, String msg, Throwable t) {
37 | if(IS_DEBUG) {
38 | Log.e(tag, checkMsg(msg), checkThrowable(t));
39 | }
40 | }
41 |
42 | public static void d(String tag, String msg, Throwable t) {
43 | if(IS_DEBUG) {
44 | Log.d(tag, checkMsg(msg), checkThrowable(t));
45 | }
46 | }
47 |
48 | public static void i(String tag, String msg, Throwable t) {
49 | if(IS_DEBUG) {
50 | Log.d(tag, checkMsg(msg), checkThrowable(t));
51 | }
52 | }
53 |
54 | public static String checkMsg(String msg) {
55 | return TextUtils.isEmpty(msg) ? "" : msg;
56 | }
57 |
58 | public static Throwable checkThrowable (Throwable t) {
59 | return null == t ? new Throwable("") : t;
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/hotimglibrary/src/main/java/com/dreamlive/hotimglibrary/utils/XMLUtils.java:
--------------------------------------------------------------------------------
1 | package com.dreamlive.hotimglibrary.utils;
2 |
3 | import android.content.Context;
4 |
5 | import com.dreamlive.hotimglibrary.entity.HotArea;
6 |
7 | import org.w3c.dom.Document;
8 | import org.w3c.dom.Element;
9 | import org.w3c.dom.NamedNodeMap;
10 | import org.w3c.dom.Node;
11 | import org.w3c.dom.NodeList;
12 |
13 | import java.io.File;
14 | import java.io.FileInputStream;
15 | import java.io.InputStream;
16 | import java.util.ArrayList;
17 | import java.util.List;
18 |
19 | import javax.xml.parsers.DocumentBuilder;
20 | import javax.xml.parsers.DocumentBuilderFactory;
21 |
22 |
23 | /**
24 | * xml解析帮助类
25 | * Created by dreamlivemeng on 2016/6/7.
26 | */
27 | public class XMLUtils {
28 |
29 | private final static String TAG = XMLUtils.class.getName();
30 |
31 | private final Context mContext;
32 |
33 | private static volatile XMLUtils mXmlUtils;
34 |
35 |
36 | private XMLUtils(Context context) {
37 | mContext = context;
38 | }
39 |
40 | public static XMLUtils getInstance(Context context) {
41 | if (mXmlUtils == null) {
42 | synchronized (XMLUtils.class) {
43 | if (mXmlUtils == null) {
44 | mXmlUtils = new XMLUtils(context);
45 | }
46 | }
47 | }
48 | return mXmlUtils;
49 | }
50 |
51 | /**
52 | * 从文件流里读取xml文档
53 | *
54 | * @param inputStream
55 | * @return
56 | */
57 | public HotArea readDoc(InputStream inputStream) {
58 | Document doc = null;
59 | HotArea root = new HotArea();
60 | DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
61 | DocumentBuilder builder;
62 | try {
63 | builder = factory.newDocumentBuilder();
64 | doc = builder.parse(inputStream);
65 | NodeList nodeList = doc.getChildNodes();
66 | if (nodeList != null && nodeList.getLength() == 1) {
67 | Node node = nodeList.item(0);
68 | root = readToPlace(null, node);
69 | }
70 | } catch (Exception e) {
71 | LogUtils.e(TAG, e.getMessage());
72 | }
73 | return root;
74 | }
75 |
76 | /**
77 | * 返回dom树
78 | *
79 | * @param fileName
80 | * @return
81 | */
82 | public HotArea readDoc(String fileName) {
83 | InputStream inputStream = null;
84 | HotArea root = null;
85 | try {
86 | inputStream = new FileInputStream(fileName);
87 | root = readDoc(inputStream);
88 | } catch (Exception e) {
89 | LogUtils.e(TAG, e.getMessage());
90 | } finally {
91 | FileUtils.closeInputStream(inputStream);
92 | }
93 | return root;
94 | }
95 |
96 | /**
97 | * 获取特定节点下属性的值,如有多个节点取第一个
98 | *
99 | * @param fileName
100 | * @param NodeName
101 | * @param attr
102 | * @return
103 | */
104 | public String readAttr(String fileName, String NodeName, String attr) {
105 | String nodeValue = "";
106 | Document doc = null;
107 | DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
108 | DocumentBuilder builder;
109 | try {
110 | builder = factory.newDocumentBuilder();
111 | doc = builder.parse(new File(fileName));
112 | NodeList nodeList = doc.getElementsByTagName(NodeName);
113 | if (nodeList != null && nodeList.getLength() == 1) {
114 | Node node = nodeList.item(0);
115 | NamedNodeMap nodeMap = node.getAttributes();
116 | Node n = nodeMap.getNamedItem(attr);
117 | nodeValue = n.getNodeValue();
118 | }
119 | } catch (Exception e) {
120 | e.printStackTrace();
121 | }
122 | return nodeValue;
123 | }
124 |
125 | public HotArea readToPlace(HotArea placeNode, Node node) {
126 | HotArea hNode = placeNode;
127 | if (hNode == null) {
128 | hNode = new HotArea();
129 | }
130 | Element element = (Element) node;
131 | NamedNodeMap attrs = element.getAttributes();
132 | int len = attrs.getLength();
133 | for (int i = 0; i < len; ++i) {
134 | Node n = attrs.item(i);
135 | String name = n.getNodeName();
136 | String value = n.getNodeValue();
137 | if ("areaId".equals(name)) {
138 | hNode.setAreaId(value);
139 | } else if ("areaTitle".equals(name)) {
140 | hNode.setAreaTitle(value);
141 | } else if ("pts".equals(name)) {
142 | hNode.setPts(value, ",");
143 | } else if ("desc".equals(name)) {
144 | hNode.setDesc(value);
145 | }
146 | }
147 |
148 | NodeList nodeList = element.getChildNodes();
149 | int jLen = nodeList.getLength();
150 | for (int j = 0; j < jLen; ++j) {
151 | Node n = nodeList.item(j);
152 | if (n instanceof Element) {
153 | HotArea h = new HotArea();
154 | hNode.getAreas().add(h);
155 | readToPlace(h, n);
156 | }
157 | }
158 |
159 | return hNode;
160 | }
161 |
162 | static class Parent {
163 |
164 | protected String title;
165 |
166 | protected String desc;
167 |
168 | protected String code;
169 |
170 | protected List places = new ArrayList();
171 |
172 | public String getCode() {
173 | return code;
174 | }
175 |
176 | public void setCode(String code) {
177 | this.code = code;
178 | }
179 |
180 | public String getTitle() {
181 | return title;
182 | }
183 |
184 | public void setTitle(String title) {
185 | this.title = title;
186 | }
187 |
188 | public String getDesc() {
189 | return desc;
190 | }
191 |
192 | public void setDesc(String desc) {
193 | this.desc = desc;
194 | }
195 |
196 | public List getPlaces() {
197 | return places;
198 | }
199 |
200 | public void setPlaces(List places) {
201 | this.places = places;
202 | }
203 | }
204 |
205 | public static class Root extends Parent {
206 |
207 | private String path;
208 |
209 | public String getPath() {
210 | return path;
211 | }
212 |
213 | public void setPath(String path) {
214 | this.path = path;
215 | }
216 | }
217 |
218 | public static class Child extends Parent {
219 |
220 | private String pointStr;
221 |
222 | private final List points = new ArrayList();
223 |
224 | public String getPointStr() {
225 | return pointStr;
226 | }
227 |
228 | public void setPointStr(String pointStr) {
229 | this.pointStr = pointStr;
230 | }
231 |
232 | public List getPoints() {
233 | return points;
234 | }
235 |
236 | public void setPoints(String pointStr) {
237 | if (null != pointStr && !"".equals(pointStr)) {
238 | String[] points = pointStr.split(",");
239 | int len = points.length;
240 | for (int i = 0; i < len; ++i) {
241 | this.points.add(Integer.parseInt(points[i]));
242 | }
243 | }
244 | }
245 | }
246 | }
247 |
--------------------------------------------------------------------------------
/hotimglibrary/src/main/java/com/dreamlive/hotimglibrary/view/HotClickView.java:
--------------------------------------------------------------------------------
1 | package com.dreamlive.hotimglibrary.view;
2 |
3 | import android.annotation.SuppressLint;
4 | import android.content.Context;
5 | import android.graphics.Bitmap;
6 | import android.graphics.BitmapFactory;
7 | import android.graphics.Canvas;
8 | import android.graphics.Color;
9 | import android.graphics.Matrix;
10 | import android.graphics.Paint;
11 | import android.graphics.Paint.Style;
12 | import android.graphics.PointF;
13 | import android.graphics.RectF;
14 | import android.os.Handler;
15 | import android.util.AttributeSet;
16 | import android.util.Log;
17 | import android.view.MotionEvent;
18 | import android.view.View;
19 | import android.view.ViewTreeObserver;
20 |
21 | import com.dreamlive.hotimglibrary.R;
22 | import com.dreamlive.hotimglibrary.entity.HotArea;
23 | import com.dreamlive.hotimglibrary.utils.LogUtils;
24 | import com.dreamlive.hotimglibrary.utils.XMLUtils;
25 |
26 | import java.io.InputStream;
27 | import java.util.HashMap;
28 | import java.util.LinkedHashSet;
29 | import java.util.Map;
30 | import java.util.Set;
31 |
32 | /**
33 | * 不规则图片区域点击响应
34 | */
35 | public class HotClickView extends View {
36 |
37 | private final static String TAG = HotClickView.class.getName();
38 |
39 | private final Context mContext;
40 | //热点区
41 | private Map mHotAreas;
42 | //检测的区域
43 | private Map mCheckAreas;
44 | //热点的Key值
45 | private Set mHotKeys;
46 | //原图片
47 | private Bitmap mSourceBitmap = null;
48 | //用于保存Matrix的值,
49 | private float[] mValues;
50 | //最大与最小缩放值
51 | private float minScale, maxScale;
52 | //
53 | private Matrix mSaveMatrix, mMatrix;
54 | //记录点的位置
55 | private PointF mPointF, mMidPointF;
56 | //触摸前两指间的距离
57 | private float mBeforeDistance;
58 | //是否是多点触摸
59 | private boolean mIsTwoFinger;
60 | //视图宽度
61 | private static int VIEW_WIDTH = 0;
62 | //视图高度
63 | private static int VIEW_HEIGHT = 0;
64 | //原图宽度
65 | private static int BIT_WIDTH = 0;
66 | //原图高度
67 | private static int BIT_HEIGHT = 0;
68 |
69 | //按下时的时间,用于检测点击事件
70 | private long mDownTime;
71 |
72 | //Path中转RectF时的中间变量,可重复利用
73 | private final RectF mEmptyRectF = new RectF();
74 |
75 |
76 | //反弹时的线程
77 | protected boolean isAnimation;
78 |
79 | //是否能移动
80 | private boolean isCanMove = true;
81 | //是否能点击
82 | private boolean isCanClick = true;
83 | //是否边界检测
84 | protected boolean isNeedToCheckOutOfSide = true;
85 | //是否能缩放
86 | private boolean isCanScale = true;
87 |
88 | private MotionEvent lastClick = null;
89 |
90 | // 控件 内边距
91 | private float mPadding = 0;
92 |
93 | private short mFitXY = 0;
94 |
95 | //不进行适配
96 | public final static short FIT_NONE = 0;
97 | //X方向适配
98 | public final static short FIT_X = 1;
99 | //Y方向适配
100 | public final static short FIT_Y = 2;
101 | //XY方向适配,以最小作为标准
102 | public final static short FIT_XY = 3;
103 |
104 | private HotArea mRootArea;
105 |
106 | // 监听 回调事件
107 | private OnClickListener mClickListener;
108 |
109 | @SuppressLint("HandlerLeak")
110 | protected Handler mViewHandler = new Handler() {
111 | public void handleMessage(android.os.Message msg) {
112 | Float[] distance = (Float[]) msg.obj;
113 | mMatrix.postTranslate(distance[0], distance[1]);
114 | invalidate();
115 | }
116 |
117 | ;
118 | };
119 |
120 | public HotClickView(Context context) {
121 | super(context);
122 | mContext = context;
123 | init();
124 | }
125 |
126 | public HotClickView(Context context, AttributeSet attrs, int defStyle) {
127 | super(context, attrs, defStyle);
128 | mContext = context;
129 | init();
130 | }
131 |
132 | public HotClickView(Context context, AttributeSet attrs) {
133 | super(context, attrs);
134 | mContext = context;
135 | init();
136 | }
137 |
138 | public void setCanScale(boolean canScale) {
139 | isCanScale = canScale;
140 | }
141 |
142 | public void setCanMove(boolean canMove) {
143 | isCanMove = canMove;
144 | }
145 |
146 | protected void init() {
147 | mCheckAreas = new HashMap();
148 | mHotAreas = new HashMap();
149 | mHotKeys = new LinkedHashSet();
150 | mPointF = new PointF();
151 | mMidPointF = new PointF();
152 | mSaveMatrix = new Matrix();
153 | mMatrix = new Matrix();
154 | mValues = new float[9];
155 | maxScale = 4f;
156 | minScale = 1f;
157 | mPadding = getResources().getDimension(R.dimen.margin_large);
158 | //获取View的高与宽,并将图片放于中间位置
159 | getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
160 | @Override
161 | public void onGlobalLayout() {
162 | getViewTreeObserver().removeGlobalOnLayoutListener(this);
163 | VIEW_HEIGHT = getHeight();
164 | VIEW_WIDTH = getWidth();
165 | moveToCenter(mSourceBitmap);
166 | scaleToFit(mSourceBitmap);
167 | }
168 | });
169 | }
170 |
171 | public void reset() {
172 | mFitXY = 0;
173 | mHotAreas.clear();
174 | mCheckAreas.clear();
175 | mHotKeys.clear();
176 | mPointF.x = 0;
177 | mPointF.y = 0;
178 | mMidPointF.x = 0;
179 | mMidPointF.y = 0;
180 | mSaveMatrix.reset();
181 | minScale = 1.0f;
182 | maxScale = 4.0f;
183 | mMatrix.reset();
184 | for (int i = 0; i < 9; ++i) {
185 | mValues[i] = 0;
186 | }
187 | }
188 |
189 | @Override
190 | protected void onDraw(Canvas canvas) {
191 | super.onDraw(canvas);
192 | if (mSourceBitmap != null) {
193 | canvas.drawBitmap(mSourceBitmap, mMatrix, null);
194 | drawPath(canvas);
195 | // drawRect(canvas);
196 | } else {
197 | LogUtils.d(TAG, "mSourceBitmap is null !");
198 | }
199 | }
200 |
201 | private void drawPath(Canvas canvas) {
202 | for (String key : mHotKeys) {
203 | float scale = getCurrentScale();
204 | Paint paint = new Paint();
205 | // paint.setColor(Color.BLUE);
206 | paint.setARGB(80, 68, 173, 161);
207 | paint.setStyle(Style.FILL);
208 | canvas.scale(scale, scale);
209 | canvas.translate((VIEW_WIDTH / scale - BIT_WIDTH) / 2, (VIEW_HEIGHT/scale-BIT_HEIGHT)
210 | / 2);
211 | canvas.drawPath(mCheckAreas.get(key).getPath(), paint);
212 | }
213 | }
214 |
215 | private void drawRect(Canvas canvas) {
216 | Paint paint = new Paint();
217 | if (mEmptyRectF != null && !mEmptyRectF.isEmpty()) {
218 | paint.setColor(Color.GREEN);
219 | paint.setStyle(Style.FILL);
220 | canvas.drawRect(mEmptyRectF, paint);
221 | }
222 | if (lastClick != null) {
223 | float[] curMove = getCurrentMoveXY();
224 | float scale = getCurrentScale();
225 | paint.setColor(Color.RED);
226 | canvas.drawCircle((lastClick.getX() - curMove[0]) / scale, (lastClick.getY() - curMove[1]) / scale, 10, paint);
227 | }
228 | }
229 |
230 | @Override
231 | public boolean onTouchEvent(MotionEvent event) {
232 | boolean resStat = super.onTouchEvent(event);
233 | if (mSourceBitmap != null && !isAnimation) {
234 | if (event.getAction() == MotionEvent.ACTION_DOWN) {
235 | mDownTime = System.currentTimeMillis();
236 | mPointF.set(event.getX(), event.getY());
237 | }
238 | if (event.getPointerCount() == 1) {
239 | if (isCanMove) {
240 | moveEvent(event);
241 | resStat = true;
242 | }
243 | if (isCanClick) {
244 | clickEvent(event);
245 | resStat = true;
246 | }
247 | } else if (event.getPointerCount() == 2) {
248 | if (isCanScale) {
249 | scaleEvent(event);
250 | resStat = true;
251 | }
252 | }
253 | if (isNeedToCheckOutOfSide) {
254 | outOfSideEvent(event);
255 | resStat = true;
256 | }
257 | }
258 | return resStat;
259 | }
260 |
261 | /**
262 | * 是否超出边界检测,进行回退到边界位置
263 | *
264 | * @param event
265 | */
266 | protected void outOfSideEvent(MotionEvent event) {
267 | switch (event.getAction()) {
268 | case MotionEvent.ACTION_UP:
269 | case MotionEvent.ACTION_CANCEL:
270 | upToCheckOutOfSide(event);
271 | break;
272 | default:
273 | break;
274 | }
275 | }
276 |
277 | /**
278 | * 点击事件检测
279 | *
280 | * @param event
281 | */
282 | protected void clickEvent(MotionEvent event) {
283 | switch (event.getAction()) {
284 | case MotionEvent.ACTION_UP:
285 | case MotionEvent.ACTION_CANCEL:
286 | upToCheckIsClick(event);
287 | break;
288 | default:
289 | break;
290 | }
291 | }
292 |
293 | /**
294 | * 移动事件检测
295 | *
296 | * @param event
297 | */
298 | protected void moveEvent(MotionEvent event) {
299 | switch (event.getAction()) {
300 | case MotionEvent.ACTION_DOWN:
301 | mSaveMatrix.set(mMatrix);
302 | break;
303 | case MotionEvent.ACTION_MOVE:
304 | if (!mIsTwoFinger) {
305 | mMatrix.set(mSaveMatrix);
306 | float moveX = event.getX() - mPointF.x;
307 | float moveY = event.getY() - mPointF.y;
308 | mMatrix.postTranslate(moveX, moveY);
309 | invalidate();
310 | }
311 | break;
312 | case MotionEvent.ACTION_CANCEL:
313 | case MotionEvent.ACTION_UP:
314 | mIsTwoFinger = false;
315 | break;
316 | default:
317 | break;
318 | }
319 | }
320 |
321 | /**
322 | * 边界反弹事件检测
323 | *
324 | * @param event
325 | */
326 | protected void upToCheckOutOfSide(MotionEvent event) {
327 | float scale = getCurrentScale();
328 | float[] moveXY = getCurrentMoveXY();
329 | float curBitWidth = scale * BIT_WIDTH;
330 | float curBitHeight = scale * BIT_HEIGHT;
331 | float[] dstXY = new float[2];
332 | boolean needMove = false;
333 | dstXY[0] = moveXY[0];
334 | dstXY[1] = moveXY[1];
335 | if (curBitHeight <= VIEW_HEIGHT) {
336 | needMove = true;
337 | dstXY[1] = (VIEW_HEIGHT - curBitHeight) / 2.0f;
338 | }
339 |
340 | if (curBitWidth <= VIEW_WIDTH) {
341 | needMove = true;
342 | dstXY[0] = (VIEW_WIDTH - curBitWidth) / 2.0f;
343 | }
344 |
345 | if (curBitHeight > VIEW_HEIGHT) {
346 | float distance = event.getY() - mPointF.y;
347 | if (distance > 0) {
348 | if (moveXY[1] > 0) {
349 | dstXY[1] = 0;
350 | needMove = true;
351 | }
352 | } else if (distance < 0) {
353 | float goalY = VIEW_HEIGHT - curBitHeight;
354 | if (moveXY[1] < goalY) {
355 | dstXY[1] = goalY;
356 | needMove = true;
357 | }
358 | }
359 | }
360 |
361 | if (curBitWidth > VIEW_WIDTH) {
362 | float distance = event.getX() - mPointF.x;
363 | if (distance > 0) {
364 | if (moveXY[0] > 0) {
365 | dstXY[0] = 0;
366 | needMove = true;
367 | }
368 | } else if (distance < 0) {
369 | float goalX = VIEW_WIDTH - curBitWidth;
370 | if (moveXY[0] < goalX) {
371 | dstXY[0] = goalX;
372 | needMove = true;
373 | }
374 | }
375 | }
376 | if (needMove) {
377 | mViewHandler.postDelayed(new MoveRunnable(moveXY[0], moveXY[1], dstXY[0], dstXY[1]), 0);
378 | }
379 | }
380 |
381 | /**
382 | * 反弹时的动画线程
383 | *
384 | * @author yq
385 | */
386 | protected class MoveRunnable implements Runnable {
387 |
388 | private final static int MOVE_STEEP = 20;
389 |
390 | private final float direct;
391 |
392 | private final boolean isMoveX;
393 |
394 | private float srcX, srcY;
395 |
396 | private final float dstX;
397 |
398 | private final float dstY;
399 |
400 | //一元一次方程
401 | private float a, b;
402 |
403 | public MoveRunnable(float srcX, float srcY, float dstX, float dstY) {
404 | this.srcX = srcX;
405 | this.srcY = srcY;
406 | this.dstX = dstX;
407 | this.dstY = dstY;
408 | //求解A,B
409 | if ((dstY - srcY) != 0 && (dstX - srcX) != 0) {
410 | a = (dstY - srcY) / (dstX - srcX);
411 | b = dstY - a * dstX;
412 | }
413 | //以长的作为出发点
414 | isMoveX = Math.abs(srcX - dstX) > Math.abs(srcY - dstY);
415 | direct = isMoveX ? ((dstX - srcX) > 0 ? 1.0f : -1.0f) : ((dstY - srcY) > 0 ? 1.0f : -1.0f);
416 | isAnimation = true;
417 | }
418 |
419 | @Override
420 | public void run() {
421 | float distanceX = 0;
422 | float distanceY = 0;
423 | boolean isEnd = false;
424 | if (isMoveX) {
425 | distanceX = direct * MOVE_STEEP;
426 | srcX += distanceX;
427 | if (direct > 0) {
428 | if (srcX >= dstX) {
429 | isEnd = true;
430 | srcX -= distanceX;
431 | distanceX = dstX - srcX;
432 | srcX = dstX;
433 | }
434 | } else {
435 | if (srcX <= dstX) {
436 | isEnd = true;
437 | srcX -= distanceX;
438 | distanceX = dstX - srcX;
439 | srcX = dstX;
440 | }
441 | }
442 |
443 | if (a == 0 && b == 0) {
444 | distanceY = 0;
445 | } else {
446 | float tempY = a * srcX + b;
447 | distanceY = tempY - srcY;
448 | srcY = tempY;
449 | }
450 | } else {
451 | distanceY = direct * MOVE_STEEP;
452 | srcY += distanceY;
453 | if (direct > 0) {
454 | if (srcY >= dstY) {
455 | isEnd = true;
456 | srcY -= distanceY;
457 | distanceY = dstY - srcY;
458 | srcY = dstY;
459 | }
460 | } else {
461 | if (srcY <= dstY) {
462 | isEnd = true;
463 | srcY -= distanceY;
464 | distanceY = dstY - srcY;
465 | srcY = dstY;
466 | }
467 | }
468 |
469 | if (a == 0 && b == 0) {
470 | distanceX = 0;
471 | } else {
472 | float tempX = (srcY - b) / a;
473 | distanceX = tempX - srcX;
474 | srcX = tempX;
475 | }
476 | }
477 | mViewHandler.obtainMessage(0, new Float[]{distanceX, distanceY}).sendToTarget();
478 | if (!isEnd) {
479 | mViewHandler.postDelayed(this, 10);
480 | } else {
481 | isAnimation = false;
482 | LogUtils.d(TAG, isAnimation + ", End!");
483 | }
484 | }
485 | }
486 |
487 | /**
488 | * 当前默认只取最前面一个
489 | *
490 | * @param
491 | */
492 | protected void upToCheckIsClick(MotionEvent event) {
493 | long curTime = System.currentTimeMillis() - mDownTime;
494 | if (curTime < 200) {
495 | checkAreas(event);
496 | if (!mHotKeys.isEmpty()) {
497 | HotArea area = null;
498 | for (String key : mHotKeys) {
499 | area = mHotAreas.get(key);
500 | mClickListener.OnClick(this, area);
501 | break;
502 | }
503 | }
504 | }
505 | }
506 |
507 | /**
508 | * 缩放事件
509 | *
510 | * @param event
511 | */
512 | protected void scaleEvent(MotionEvent event) {
513 | switch (event.getAction() & MotionEvent.ACTION_MASK) {
514 | case MotionEvent.ACTION_POINTER_DOWN:
515 | mIsTwoFinger = true;
516 | mBeforeDistance = spacing(event);
517 | if (mBeforeDistance > 10f) {
518 | mSaveMatrix.set(mMatrix);
519 | }
520 | break;
521 | case MotionEvent.ACTION_MOVE:
522 | float afterDistance = spacing(event);
523 | if (afterDistance > 10f) {
524 | float tempScale = afterDistance / mBeforeDistance;
525 | mMatrix.set(mSaveMatrix);
526 | float newScale = getCheckRangeScale(tempScale);
527 | // imageCenterLocation(event);
528 | mMatrix.postScale(newScale, newScale, VIEW_WIDTH / 2, VIEW_HEIGHT / 2);
529 | invalidate();
530 | }
531 | break;
532 | case MotionEvent.ACTION_POINTER_UP:
533 | if (event.getPointerCount() == 0) {
534 | upToCheckOutOfSide(event);
535 | }
536 | break;
537 | default:
538 | break;
539 | }
540 | }
541 |
542 | /**
543 | * 两点触摸时的距离
544 | *
545 | * @param event
546 | * @return
547 | */
548 | @SuppressLint("FloatMath")
549 | protected float spacing(MotionEvent event) {
550 | float x = event.getX(0) - event.getX(1);
551 | float y = event.getY(0) - event.getY(1);
552 | return (float) Math.sqrt(x * x + y * y);
553 | }
554 |
555 | /**
556 | * 返回当前的ScaleX
557 | */
558 | public float getCurrentScale() {
559 | mMatrix.getValues(mValues);
560 | return Math.abs(mValues[0] == 0 ? mValues[1] : mValues[0]);
561 | }
562 |
563 | /**
564 | * 返回当前的移动位置
565 | *
566 | * @return
567 | */
568 | public float[] getCurrentMoveXY() {
569 | mMatrix.getValues(mValues);
570 | return new float[]{mValues[2], mValues[5]};
571 | }
572 |
573 | /**
574 | * 检测是否在当前的缩放范围里
575 | *
576 | * @param tempScale
577 | * @return
578 | */
579 | protected float getCheckRangeScale(float tempScale) {
580 | mMatrix.getValues(mValues);
581 | float value = Math.abs(mValues[0]) + Math.abs(mValues[1]);
582 | float newScale = value * tempScale;
583 | if (newScale < minScale)
584 | return minScale / value;
585 | else if (newScale > maxScale)
586 | return maxScale / value;
587 | return tempScale;
588 | }
589 |
590 | /**
591 | * 缩放的中心位置
592 | *
593 | * @param event
594 | */
595 | protected void imageCenterLocation(MotionEvent event) {
596 | mMidPointF.x = (event.getX(0) + event.getX(1)) / 2;
597 | mMidPointF.y = (event.getY(0) + event.getY(1)) / 2;
598 | }
599 |
600 | /**
601 | * 是否点击在热点区检测
602 | *
603 | * @param event
604 | */
605 | protected void checkAreas(MotionEvent event) {
606 | mHotKeys.clear();
607 | float[] curMove = getCurrentMoveXY();
608 | float scale = getCurrentScale();
609 | for (String key : mCheckAreas.keySet()) {
610 | if (mCheckAreas.get(key).isInArea(mEmptyRectF, (event.getX() - curMove[0]) / scale, (event.getY() - curMove[1]) / scale)) {
611 | mHotKeys.add(key);
612 | lastClick = event;
613 | break;
614 | }
615 | }
616 | }
617 |
618 | /**
619 | * @param bitmap
620 | */
621 | public void setImageBitmap(Bitmap bitmap) {
622 | setImageBitmap(bitmap, FIT_NONE);
623 | }
624 |
625 | /**
626 | * 设置图片
627 | *
628 | * @param bitmap
629 | */
630 | public void setImageBitmap(Bitmap bitmap, short fitXY) {
631 | reset();
632 | mFitXY = fitXY;
633 | isCanClick = false;
634 | mSourceBitmap = bitmap;
635 | if (mSourceBitmap != null) {
636 | BIT_WIDTH = mSourceBitmap.getWidth();
637 | BIT_HEIGHT = mSourceBitmap.getHeight();
638 | }
639 | invalidate();
640 | }
641 |
642 | public void setImageBitmap(String hotFilePath, String hotImgPath) {
643 | setImageBitmap(hotFilePath, hotImgPath, FIT_NONE);
644 | }
645 |
646 | /**
647 | * 从资源文件中加载xml与picture
648 | *
649 | * @param hotFilePath:热点区域的位置定义文件(.xml)
650 | * @param hotImgPath:图片位置
651 | * @param fitXY [0, 1, 2, 3]
652 | * 0: no fit
653 | * 1: fitx
654 | * 2: fity
655 | * 3: fitxy get min scale X,Y
656 | */
657 | public void setImageBitmap(String hotFilePath, String hotImgPath, short fitXY) {
658 | reset();
659 | mFitXY = fitXY;
660 | mRootArea = XMLUtils.getInstance(mContext).readDoc(hotFilePath);
661 | mSourceBitmap = BitmapFactory.decodeFile(hotImgPath);
662 | resetFiles();
663 | }
664 |
665 | /**
666 | * 设置图片,从流中加载
667 | *
668 | * @param hotFileStream
669 | * @param hotImgStream 0: no fit
670 | * 1: fitx
671 | * 2: fity
672 | * 3: fitxy get min scale X,Y
673 | */
674 | public void setImageBitmap(InputStream hotFileStream, InputStream hotImgStream) {
675 | setImageBitmap(hotFileStream, hotImgStream, FIT_NONE);
676 | }
677 |
678 | /**
679 | * 设置图片,从流中加载
680 | *
681 | * @param hotFileStream
682 | * @param hotImgStream
683 | * @param fitXY [0, 1, 2, 3]
684 | * 0: no fit
685 | * 1: fitx
686 | * 2: fity
687 | * 3: fitxy get min scale X,Y
688 | */
689 | public void setImageBitmap(InputStream hotFileStream, InputStream hotImgStream, short fitXY) {
690 | reset();
691 | mFitXY = fitXY;
692 | mRootArea = XMLUtils.getInstance(mContext).readDoc(hotFileStream);
693 | mSourceBitmap = BitmapFactory.decodeStream(hotImgStream);
694 | resetFiles();
695 | }
696 |
697 | /**
698 | * 图片信息重置
699 | */
700 | protected void resetFiles() {
701 | try {
702 | if (mSourceBitmap != null) {
703 | BIT_HEIGHT = mSourceBitmap.getHeight();
704 | BIT_WIDTH = mSourceBitmap.getWidth();
705 | }
706 | moveToCenter(mSourceBitmap);
707 | scaleToFit(mSourceBitmap);
708 | addToKeys(mRootArea);
709 | invalidate();
710 | } catch (Exception e) {
711 | LogUtils.e(TAG, e.getMessage());
712 | }
713 | }
714 |
715 | /**
716 | * 移动到中间位置
717 | *
718 | * @param bitmap
719 | */
720 | protected void moveToCenter(Bitmap bitmap) {
721 | if (bitmap != null && VIEW_HEIGHT != 0 && VIEW_WIDTH != 0) {
722 | mMatrix.setTranslate((VIEW_WIDTH - BIT_WIDTH) / 2, (VIEW_HEIGHT - BIT_HEIGHT) / 2);
723 | }
724 | }
725 |
726 | /**
727 | * 缩放到合适大小
728 | *
729 | * @param bitmap
730 | */
731 | protected void scaleToFit(Bitmap bitmap) {
732 | if (bitmap != null && VIEW_HEIGHT != 0 && VIEW_WIDTH != 0) {
733 | float newScaleX = minScale;
734 | float newScaleY = minScale;
735 | if (mFitXY == 1 || mFitXY == 3) {
736 | // if(BIT_WIDTH > VIEW_WIDTH) {
737 | newScaleX = (VIEW_WIDTH * 1.0f) / BIT_WIDTH;
738 | LogUtils.d(TAG, "newScaleX:" + newScaleX);
739 | // }
740 | }
741 | if (mFitXY == 2 || mFitXY == 3) {
742 | // if(BIT_HEIGHT > VIEW_HEIGHT) {
743 | newScaleY = ((VIEW_HEIGHT - mPadding * 2) * 1.0f) / BIT_HEIGHT;
744 | LogUtils.d(TAG, "newScaleY:" + newScaleY);
745 | // }
746 | }
747 | minScale = Math.min(newScaleX, newScaleY);
748 | mMatrix.postScale(minScale, minScale, VIEW_WIDTH / 2, VIEW_HEIGHT / 2);
749 | }
750 | }
751 |
752 | protected void scaleToFit1(Bitmap bitmap) {
753 | if (bitmap != null && VIEW_HEIGHT != 0 && VIEW_WIDTH != 0) {
754 | float newScaleX = minScale;
755 | float newScaleY = minScale;
756 | if (mFitXY == 1 || mFitXY == 3) {
757 | newScaleX = (VIEW_WIDTH * 1.0f) / BIT_WIDTH;
758 | Log.d(TAG, "newScaleX:" + newScaleX);
759 | }
760 | if (mFitXY == 2 || mFitXY == 3) {
761 | newScaleY = ((VIEW_HEIGHT - mPadding * 2) * 1.0f) / BIT_HEIGHT;
762 | Log.d(TAG, "newScaleY:" + newScaleY);
763 | }
764 | minScale = Math.min(newScaleX, newScaleY);
765 | mMatrix.postScale(minScale, minScale, VIEW_WIDTH / 2,
766 | VIEW_HEIGHT / 2);
767 | }
768 | }
769 |
770 | public HotArea getRootArea() {
771 | return mRootArea;
772 | }
773 |
774 | /**
775 | * 添加到热点集合中
776 | *
777 | * @param area
778 | */
779 | protected void addToKeys(HotArea area) {
780 | if (area != null) {
781 | String areaCode = area.getAreaId();
782 | HotArea.CheckArea checkArea = area.getCheckArea();
783 | mHotAreas.put(areaCode, area);
784 | if (checkArea != null) {
785 | mCheckAreas.put(areaCode, checkArea);
786 | }
787 | for (HotArea hot : area.getAreas()) {
788 | addToKeys(hot);
789 | }
790 | }
791 | }
792 |
793 | /**
794 | * 设置点击 事件
795 | */
796 | public void setOnClickListener(OnClickListener listener) {
797 | this.mClickListener = listener;
798 | }
799 |
800 | /**
801 | * 点击 热图 区域 选中效果
802 | */
803 | public interface OnClickListener {
804 | public void OnClick(View view, HotArea hotArea);
805 | }
806 | }
807 |
--------------------------------------------------------------------------------
/hotimglibrary/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 | 20dp
7 |
8 |
--------------------------------------------------------------------------------
/hotimglibrary/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | HotImgLibrary
3 |
4 |
--------------------------------------------------------------------------------
/hotimglibrary/src/test/java/com/dreamlive/hotimglibrary/ExampleUnitTest.java:
--------------------------------------------------------------------------------
1 | package com.dreamlive.hotimglibrary;
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() throws Exception {
15 | assertEquals(4, 2 + 2);
16 | }
17 | }
--------------------------------------------------------------------------------
/images/xiaoguo.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dreamlivemeng/HotImg/03a4033c788b87bb7ded7be7a029524a9324f39e/images/xiaoguo.gif
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app', ':hotimglibrary'
2 |
--------------------------------------------------------------------------------