├── .gitignore
├── .idea
├── compiler.xml
├── copyright
│ └── profiles_settings.xml
├── encodings.xml
├── gradle.xml
├── misc.xml
├── modules.xml
├── runConfigurations.xml
└── vcs.xml
├── README.md
├── app
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── com
│ │ └── junmeng
│ │ └── region
│ │ └── ExampleInstrumentedTest.java
│ ├── main
│ ├── AndroidManifest.xml
│ ├── java
│ │ └── com
│ │ │ └── junmeng
│ │ │ └── region
│ │ │ ├── MainActivity.java
│ │ │ └── RegionDetectSViewActivity.java
│ └── res
│ │ ├── drawable
│ │ ├── ic_china.xml
│ │ └── ic_chinalow.xml
│ │ ├── layout
│ │ ├── activity_main.xml
│ │ ├── activity_region_detect_sview.xml
│ │ └── activity_region_detect_view.xml
│ │ ├── menu
│ │ └── menu_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
│ └── junmeng
│ └── region
│ └── ExampleUnitTest.java
├── build.gradle
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── lib_regiondetector
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ ├── main
│ ├── AndroidManifest.xml
│ ├── java
│ │ └── com
│ │ │ └── junmeng
│ │ │ └── rdetecte
│ │ │ ├── bean
│ │ │ ├── MapPathInfo.java
│ │ │ └── VectorPathInfo.java
│ │ │ ├── utils
│ │ │ ├── CommonUtil.java
│ │ │ ├── PathParser.java
│ │ │ └── VectorMapParser.java
│ │ │ └── widget
│ │ │ ├── BaseSurfaceView.java
│ │ │ └── RegionDetectSurfaceView.java
│ └── res
│ │ ├── drawable
│ │ ├── ic_location_24dp.xml
│ │ └── ic_map_china.xml
│ │ └── values
│ │ └── strings.xml
│ └── test
│ └── java
│ └── com
│ └── junmeng
│ └── rdetecte
│ └── ExampleUnitTest.java
├── screenshots
├── QQ截图20170220173408.png
├── device-2017-02-20-174131.mp4_1487584213.gif
├── device-2017-02-20-174131.mp4_1487584269.gif
└── device-2017-02-20-174131.mp4_1487584566.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 | .externalNativeBuild
10 |
--------------------------------------------------------------------------------
/.idea/compiler.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/.idea/copyright/profiles_settings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/.idea/encodings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/gradle.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
18 |
19 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 | 1.8
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/.idea/runConfigurations.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # RegionDetector
2 | 一个支持灵活设置的不规则区域检测控件
3 | ---
4 |
5 | 
6 |
7 | 
8 | 
9 | 
10 | ---
11 | ## 更新说明
12 | > 1.1.0
13 | * 去除反射
14 | * 增加矢量图解析器,路径解析器
15 | * 兼容到API27
16 |
17 |
18 | ---
19 | ## 介绍说明
20 |
21 | * 此控件支持不规则区域的识别监测,采用矢量图,有效降低内存,常用来做地图区域识别
22 | * 支持手动点击模式和中心点定位模式
23 | * 支持缩放位移操作
24 | * 支持双击缩放操作
25 | * 提供渐变动画,拒绝生硬
26 | * 支持自定义矢量图,但需满足一定要求
27 | * 开放众多接口满足个性化定制
28 | * ...
29 |
30 | ---
31 | ## 原理介绍
32 | [实现思路介绍](http://blog.csdn.net/huweijian5/article/details/78921793)
33 |
34 | ---
35 | ## 使用说明
36 | * 布局xml中添加:
37 | ```
38 |
42 | ```
43 | * 设置激活区域:
44 | ```java
45 | int[] areaRes = new int[]{
46 | R.string.china_anhui, R.string.china_beijing, R.string.china_guangdong
47 | , R.string.china_chongqing, R.string.china_xinjiang, R.string.china_fujian
48 | , R.string.china_gansu, R.string.china_zhejiang, R.string.china_yunnan
49 | , R.string.china_xizang, R.string.china_tianjin
50 | , R.string.china_shandong, R.string.china_heilongjiang, R.string.china_hainan
51 | };
52 | binding.rdvDetect.setAreaActivateStatus(areaRes, true);
53 | ```
54 | * 全部区域请查看:
55 | ```
56 |
57 | 北京
58 | 天津
59 | 上海
60 | 重庆
61 | 河北
62 | 山西
63 | 辽宁
64 | 吉林
65 | 黑龙江
66 | 江苏
67 | 浙江
68 | 安徽
69 | 福建
70 | 江西
71 | 山东
72 | 河南
73 | 湖北
74 | 湖南
75 | 广东
76 | 海南
77 | 四川
78 | 贵州
79 | 云南
80 | 陕西
81 | 甘肃
82 | 青海
83 | 台湾
84 | 内蒙古
85 | 广西
86 | 西藏
87 | 宁夏
88 | 新疆
89 | 香港
90 |
91 | ```
92 | * 设置监听器:
93 | ```
94 | binding.rdvDetect.setOnActivateRegionDetectListener(new RegionDetectSurfaceView.OnActivateRegionDetectListener() {
95 | @Override
96 | public void onActivateRegionDetect(String name) {
97 | binding.tvActivate.setText("高亮区域:" + name);
98 | binding.rdvDetect.setSelectedAreaOnlyCloseCenterLocation(name);
99 | }
100 | });
101 | binding.rdvDetect.setOnRegionDetectListener(new RegionDetectSurfaceView.OnRegionDetectListener() {
102 | @Override
103 | public void onRegionDetect(String name) {
104 | binding.tvDetect.setText("当前区域:" + name);
105 | }
106 | });
107 |
108 | binding.rdvDetect.setOnDoubleClickListener(new RegionDetectSurfaceView.OnDoubleClickListener() {
109 | @Override
110 | public void onDoubleClick(@RegionDetectSurfaceView.ScaleMode int scaleMode) {
111 | binding.tvZoom.setText("双击操作:" + (scaleMode == SCALE_ZOOMIN ? "放大" : "缩小"));
112 | }
113 | });
114 | ```
115 | * 至此最简单的引用方式已经结束了。
116 |
117 | ### 常用api说明:
118 |
119 | > public void setRegionDetectMode(@RegionDetectMode int detectMode);
120 | * 可在此设置支持的模式为手动点击模式和中心定位模式
121 | * 手动点击模式符合一般使用习惯,但区域较小时很难点击
122 | * 中心定位模式则可以较细致的定位,推荐使用,默认也是此模式
123 |
124 | ---
125 | > public void setCenterIcon(Bitmap bitmap);
126 | * 自定义中心定位图标
127 |
128 | ---
129 | > public void setCenterIconLocationType(@CenterIconLocationType int locationType);
130 | * 设置中心定位图标的定位位置,支持图标中心和图标底部两种
131 |
132 | ---
133 | > public void fitCenter();
134 | * 将地图适合屏幕缩放并居中
135 |
136 | ---
137 | > public void setAreaColor(@NonNull String areaName, @ColorInt int highlightColor, @ColorInt int activatedColor, @ColorInt int normalColor);
138 | * 设置区域高亮颜色,激活颜色和普通颜色
139 | * 此设置优先级高于默认的颜色,如上图的广东会变成黄色
140 |
141 | ---
142 | > public void setAreaMap(@DrawableRes int map, int originalWidth, int originalHeight);
143 | * 自定义区域地图
144 | * 矢量图格式需要如下:
145 | ```
146 |
151 |
152 |
159 |
160 |
167 |
168 |
175 | ......
176 |
177 | ```
178 | * 其中vector元素的viewportHeight和viewportWidth以及path元素的name和pathData必须设置
179 |
180 | ---
181 | > 更多请详看在线文档或源码及参照demo
182 |
183 | ---
184 | ## JavaDoc文档
185 |
186 | * [在线JavaDoc](https://jitpack.io/com/github/huweijian5/RegionDetector/1.0.0/javadoc/index.html)
187 | * 网址:`https://jitpack.io/com/github/huweijian5/RegionDetector/[VersionCode]/javadoc/index.html`
188 | * 其中[VersionCode](https://github.com/huweijian5/RegionDetector/releases)请替换为最新版本号
189 | * 注意文档使用UTF-8编码,如遇乱码,请在浏览器选择UTF-8编码即可
190 |
191 | ---
192 | ## 引用
193 |
194 | * 如果需要引用此库,做法如下:
195 | * Add it in your root build.gradle at the end of repositories:
196 | ```
197 | allprojects {
198 | repositories {
199 | ...
200 | maven { url "https://jitpack.io" }
201 | }
202 | }
203 | ```
204 | * and then,add the dependecy:
205 | ```
206 | dependencies {
207 | compile 'com.github.huweijian5:RegionDetector:latest_version'
208 | }
209 | ```
210 | * 其中latest_version请到[releases](https://github.com/huweijian5/RegionDetector/releases)中查看
211 |
212 | ## 注意
213 | * 为了避免引入第三方库导致工程依赖多个版本的问题,如android support库
214 | * 故建议在个人的工程目录下的build.gradle下加入以下变量,具体请看此[build.gradle](https://github.com/huweijian5/RegionDetector/blob/master/build.gradle)
215 | ```
216 | ext{
217 | minSdkVersion = 16
218 | targetSdkVersion = 25
219 | compileSdkVersion = 26
220 | buildToolsVersion = '26.0.1'
221 |
222 | // App dependencies
223 | supportLibraryVersion = '26.1.0'
224 | junitVersion = '4.12'
225 | espressoVersion = '2.2.2'
226 | }
227 | ```
228 | * 请注意,对于此库已有的变量,命名请保持一致
229 |
230 |
231 |
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | android {
4 | dataBinding{
5 | enabled=true
6 | }
7 |
8 | compileSdkVersion rootProject.compileSdkVersion
9 | buildToolsVersion rootProject.buildToolsVersion
10 | defaultConfig {
11 | applicationId "com.junmeng.region"
12 | minSdkVersion rootProject.minSdkVersion
13 | targetSdkVersion rootProject.targetSdkVersion
14 | versionCode 1
15 | versionName "1.0"
16 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
17 | vectorDrawables.useSupportLibrary = true
18 | }
19 | buildTypes {
20 | release {
21 | minifyEnabled false
22 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
23 | }
24 | }
25 | }
26 |
27 | dependencies {
28 | compile fileTree(include: ['*.jar'], dir: 'libs')
29 | androidTestCompile("com.android.support.test.espresso:espresso-core:$rootProject.espressoVersion", {
30 | exclude group: 'com.android.support', module: 'support-annotations'
31 | })
32 | compile "com.android.support:appcompat-v7:$rootProject.supportLibraryVersion"
33 | testCompile "junit:junit:$rootProject.junitVersion"
34 |
35 | compile project(':lib_regiondetector')
36 | }
37 |
--------------------------------------------------------------------------------
/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 E:\Android\sdk/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/com/junmeng/region/ExampleInstrumentedTest.java:
--------------------------------------------------------------------------------
1 | package com.junmeng.region;
2 |
3 | import android.content.Context;
4 | import android.support.test.InstrumentationRegistry;
5 | import android.support.test.runner.AndroidJUnit4;
6 |
7 | import org.junit.Test;
8 | import org.junit.runner.RunWith;
9 |
10 | import static org.junit.Assert.*;
11 |
12 | /**
13 | * Instrumentation test, which will execute on an Android device.
14 | *
15 | * @see Testing documentation
16 | */
17 | @RunWith(AndroidJUnit4.class)
18 | public class ExampleInstrumentedTest {
19 | @Test
20 | public void useAppContext() throws Exception {
21 | // Context of the app under test.
22 | Context appContext = InstrumentationRegistry.getTargetContext();
23 |
24 | assertEquals("com.junmeng.region", appContext.getPackageName());
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/app/src/main/java/com/junmeng/region/MainActivity.java:
--------------------------------------------------------------------------------
1 | package com.junmeng.region;
2 |
3 | import android.content.Intent;
4 | import android.databinding.DataBindingUtil;
5 | import android.os.Bundle;
6 | import android.support.v7.app.AppCompatActivity;
7 | import android.support.v7.app.AppCompatDelegate;
8 | import android.view.View;
9 |
10 | import com.junmeng.region.databinding.ActivityMainBinding;
11 |
12 | public class MainActivity extends AppCompatActivity {
13 | static {
14 | AppCompatDelegate.setCompatVectorFromResourcesEnabled(true);
15 | }
16 |
17 | ActivityMainBinding binding;
18 |
19 | @Override
20 | protected void onCreate(Bundle savedInstanceState) {
21 | super.onCreate(savedInstanceState);
22 | binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
23 | }
24 |
25 |
26 | public void onClickRDSV(View view) {
27 | Intent intent = new Intent(this, RegionDetectSViewActivity.class);
28 | startActivity(intent);
29 | }
30 |
31 | public void onClickRDV(View view) {
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/app/src/main/java/com/junmeng/region/RegionDetectSViewActivity.java:
--------------------------------------------------------------------------------
1 | package com.junmeng.region;
2 |
3 | import android.databinding.DataBindingUtil;
4 | import android.graphics.Color;
5 | import android.os.Bundle;
6 | import android.support.v7.app.AppCompatActivity;
7 | import android.util.Log;
8 | import android.view.Menu;
9 | import android.view.MenuItem;
10 |
11 | import com.junmeng.rdetecte.utils.CommonUtil;
12 | import com.junmeng.rdetecte.utils.VectorMapParser;
13 | import com.junmeng.rdetecte.widget.RegionDetectSurfaceView;
14 | import com.junmeng.region.databinding.ActivityRegionDetectSviewBinding;
15 |
16 | import static com.junmeng.rdetecte.widget.RegionDetectSurfaceView.SCALE_ZOOMIN;
17 |
18 | public class RegionDetectSViewActivity extends AppCompatActivity {
19 | private static final String TAG = "RegionDetectSViewActivi";
20 | int[] areaRes = new int[]{
21 | R.string.china_anhui, R.string.china_beijing, R.string.china_guangdong
22 | , R.string.china_chongqing, R.string.china_xinjiang, R.string.china_fujian
23 | , R.string.china_gansu, R.string.china_zhejiang, R.string.china_yunnan
24 | , R.string.china_xizang, R.string.china_tianjin
25 | , R.string.china_shandong, R.string.china_heilongjiang, R.string.china_hainan
26 | };
27 |
28 | int[] areaRes2 = new int[]{
29 | R.string.china_anhui, R.string.china_beijing, R.string.china_guangdong
30 | , R.string.china_chongqing, R.string.china_shanxi, R.string.china_neimenggu
31 | , R.string.china_fujian, R.string.china_gansu, R.string.china_zhejiang
32 | , R.string.china_yunnan, R.string.china_liaoning, R.string.china_tianjin
33 | , R.string.china_shandong, R.string.china_heilongjiang, R.string.china_hainan
34 | };
35 |
36 | ActivityRegionDetectSviewBinding binding;
37 |
38 | @Override
39 | protected void onCreate(Bundle savedInstanceState) {
40 | super.onCreate(savedInstanceState);
41 | binding = DataBindingUtil.setContentView(this, R.layout.activity_region_detect_sview);
42 | binding.rdvDetect.setOnActivateRegionDetectListener(new RegionDetectSurfaceView.OnActivateRegionDetectListener() {
43 | @Override
44 | public void onActivateRegionDetect(String name) {
45 | binding.tvActivate.setText("高亮区域:" + name);
46 | binding.rdvDetect.setSelectedAreaOnlyCloseCenterLocation(name);
47 | }
48 | });
49 | binding.rdvDetect.setOnRegionDetectListener(new RegionDetectSurfaceView.OnRegionDetectListener() {
50 | @Override
51 | public void onRegionDetect(String name) {
52 | binding.tvDetect.setText("当前区域:" + name);
53 | }
54 | });
55 |
56 | binding.rdvDetect.setOnDoubleClickListener(new RegionDetectSurfaceView.OnDoubleClickListener() {
57 | @Override
58 | public void onDoubleClick(@RegionDetectSurfaceView.ScaleMode int scaleMode) {
59 | binding.tvZoom.setText("双击操作:" + (scaleMode == SCALE_ZOOMIN ? "放大" : "缩小"));
60 | }
61 | });
62 |
63 | //binding.rdvDetect.setCenterIcon(CommonUtil.getBitmapFromVectorDrawable(this,R.mipmap.ic_launcher));
64 | //binding.rdvDetect.setAllAreaActivateStatus(true);
65 | binding.rdvDetect.setAreaActivateStatus(areaRes, true);
66 | binding.rdvDetect.setAreaColor(R.string.china_guangdong, Color.YELLOW, -1, -1);
67 | binding.rdvDetect.setCenterIconVisibility(true);
68 | //binding.rdvDetect.setCenterIconLocationType(RegionDetectSurfaceView.CENTER_ICON_POSITION_CENTER);
69 | binding.rdvDetect.setDefaultNormalColor(0x8069BBA8);
70 | binding.rdvDetect.setDefaultActivateColor(0x802F8BBB);
71 | binding.rdvDetect.setDefaultHighlightColor(0x80BB945A);
72 | binding.rdvDetect.setBackgroundColor(0xFFdddddd);
73 | }
74 |
75 |
76 | @Override
77 | public boolean onCreateOptionsMenu(Menu menu) {
78 | getMenuInflater().inflate(R.menu.menu_main, menu);
79 | return super.onCreateOptionsMenu(menu);
80 | }
81 |
82 | @Override
83 | public boolean onOptionsItemSelected(MenuItem item) {
84 | int id = item.getItemId();
85 | Log.i(TAG, "onContextItemSelected: ");
86 | switch (id) {
87 | case R.id.m_fitcenter:
88 | binding.rdvDetect.fitCenter();
89 | break;
90 | case R.id.m_change_icon:
91 | binding.rdvDetect.setCenterIcon(CommonUtil.getBitmapFromVectorDrawable(this,R.mipmap.ic_launcher));
92 | break;
93 | case R.id.m_toggle:
94 | binding.rdvDetect.setAreaMap(R.drawable.ic_china);
95 | binding.rdvDetect.setAreaActivateStatus(areaRes2, true);
96 | binding.rdvDetect.setAreaColor(R.string.china_guangdong, Color.YELLOW, -1, -1);
97 | binding.rdvDetect.setDefaultNormalColor(0x8069BBA8);
98 | binding.rdvDetect.setDefaultActivateColor(0x802F8BBB);
99 | binding.rdvDetect.setDefaultHighlightColor(0x80BB945A);
100 | binding.rdvDetect.setBackgroundColor(0xFFFFCAF3);
101 | break;
102 | case R.id.m_mode_center:
103 | binding.rdvDetect.setRegionDetectMode(RegionDetectSurfaceView.REGION_DETECT_MODE_CENTER);
104 | break;
105 | case R.id.m_mode_click:
106 | binding.rdvDetect.setRegionDetectMode(RegionDetectSurfaceView.REGION_DETECT_MODE_CLICK);
107 | break;
108 | }
109 | return super.onOptionsItemSelected(item);
110 | }
111 | }
112 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_china.xml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
22 |
23 |
24 |
28 |
29 |
33 |
34 |
38 |
39 |
43 |
44 |
48 |
49 |
53 |
54 |
58 |
59 |
63 |
64 |
68 |
69 |
73 |
74 |
78 |
79 |
83 |
84 |
88 |
89 |
93 |
94 |
98 |
99 |
103 |
104 |
108 |
109 |
113 |
114 |
118 |
119 |
123 |
124 |
128 |
129 |
133 |
134 |
138 |
139 |
143 |
144 |
148 |
149 |
172 |
173 |
177 |
178 |
182 |
183 |
187 |
188 |
192 |
193 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
15 |
16 |
17 |
23 |
24 |
30 |
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_region_detect_sview.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
14 |
15 |
19 |
20 |
24 |
25 |
30 |
31 |
36 |
37 |
42 |
43 |
44 |
45 |
46 |
47 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_region_detect_view.xml:
--------------------------------------------------------------------------------
1 |
2 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/menu_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/android-coding-well/RegionDetector/534469597d194ec162462ff8ef5773239247cb4d/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/android-coding-well/RegionDetector/534469597d194ec162462ff8ef5773239247cb4d/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/android-coding-well/RegionDetector/534469597d194ec162462ff8ef5773239247cb4d/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/android-coding-well/RegionDetector/534469597d194ec162462ff8ef5773239247cb4d/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/android-coding-well/RegionDetector/534469597d194ec162462ff8ef5773239247cb4d/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 | RegionDetector
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/app/src/test/java/com/junmeng/region/ExampleUnitTest.java:
--------------------------------------------------------------------------------
1 | package com.junmeng.region;
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 | google()
7 | }
8 | dependencies {
9 | classpath 'com.android.tools.build:gradle:3.0.1'
10 | classpath 'com.github.dcendents:android-maven-gradle-plugin:1.5'
11 | // NOTE: Do not place your application dependencies here; they belong
12 | // in the individual module build.gradle files
13 | }
14 | }
15 |
16 | allprojects {
17 | repositories {
18 | jcenter()
19 | google()
20 | maven { url 'https://jitpack.io' }
21 | }
22 | }
23 |
24 | task clean(type: Delete) {
25 | delete rootProject.buildDir
26 | }
27 | ext {
28 | minSdkVersion = 16//最低支持19
29 | targetSdkVersion = 25
30 | compileSdkVersion = 26
31 | buildToolsVersion = '26.0.2'
32 |
33 | // App dependencies
34 | supportLibraryVersion = '26.1.0'
35 | junitVersion = '4.12'
36 | espressoVersion = '2.2.2'
37 | }
--------------------------------------------------------------------------------
/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/android-coding-well/RegionDetector/534469597d194ec162462ff8ef5773239247cb4d/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-4.1-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 |
--------------------------------------------------------------------------------
/lib_regiondetector/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/lib_regiondetector/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 | apply plugin: 'com.github.dcendents.android-maven'
3 | group = 'com.github.huweijian5'
4 | android {
5 | compileSdkVersion rootProject.compileSdkVersion
6 | buildToolsVersion rootProject.buildToolsVersion
7 |
8 | defaultConfig {
9 | minSdkVersion rootProject.minSdkVersion
10 | targetSdkVersion rootProject.targetSdkVersion
11 | versionCode 1
12 | versionName "1.0"
13 |
14 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
15 | vectorDrawables.useSupportLibrary = true
16 | }
17 | buildTypes {
18 | release {
19 | minifyEnabled false
20 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
21 | }
22 | }
23 | }
24 |
25 | dependencies {
26 | compile fileTree(dir: 'libs', include: ['*.jar'])
27 | compile "com.android.support:appcompat-v7:$rootProject.supportLibraryVersion"
28 | testCompile "junit:junit:$rootProject.junitVersion"
29 | compile 'com.github.huweijian5:GestureDetectorView:1.0.3'
30 | }
31 | // 指定编码
32 | tasks.withType(JavaCompile) {
33 | options.encoding = "UTF-8"
34 | }
35 |
36 | // 打包源码
37 | task sourcesJar(type: Jar) {
38 | from android.sourceSets.main.java.srcDirs
39 | classifier = 'sources'
40 | }
41 |
42 | task javadoc(type: Javadoc) {
43 | failOnError false
44 | source = android.sourceSets.main.java.sourceFiles
45 | classpath += project.files(android.getBootClasspath().join(File.pathSeparator))
46 | classpath += configurations.compile
47 | }
48 |
49 | // 制作文档(Javadoc)
50 | task javadocJar(type: Jar, dependsOn: javadoc) {
51 | classifier = 'javadoc'
52 | from javadoc.destinationDir
53 | }
54 |
55 | artifacts {
56 | archives sourcesJar
57 | archives javadocJar
58 | }
--------------------------------------------------------------------------------
/lib_regiondetector/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 E:\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 |
--------------------------------------------------------------------------------
/lib_regiondetector/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/lib_regiondetector/src/main/java/com/junmeng/rdetecte/bean/MapPathInfo.java:
--------------------------------------------------------------------------------
1 | package com.junmeng.rdetecte.bean;
2 |
3 | import android.graphics.Path;
4 | import android.graphics.Region;
5 |
6 | import com.junmeng.rdetecte.utils.CommonUtil;
7 |
8 | /**
9 | * 地图信息
10 | * Created by HWJ on 2017/2/19.
11 | */
12 |
13 | public class MapPathInfo {
14 |
15 | public MapPathInfo(Path path){
16 | this.path=path;
17 | region= CommonUtil.genRegion(path);
18 | }
19 |
20 | /**
21 | * 路径区域
22 | */
23 | public Region region;
24 | /**
25 | * 区域路径
26 | */
27 | public Path path;
28 | /**
29 | * 是否激活,激活的才能被选中高亮显示
30 | */
31 | public boolean isActivated=false;
32 | /**
33 | * 激活颜色,优先级最高,-1表示未设置,采用默认
34 | */
35 | public int activatedColor= -1;
36 | /**
37 | * 高亮颜色,优先级最高,-1表示未设置,采用默认
38 | */
39 | public int highlightColor=-1;
40 | /**
41 | * 正常颜色,优先级最高,-1表示未设置,采用默认
42 | */
43 | public int normalColor=-1;
44 | }
45 |
--------------------------------------------------------------------------------
/lib_regiondetector/src/main/java/com/junmeng/rdetecte/bean/VectorPathInfo.java:
--------------------------------------------------------------------------------
1 | package com.junmeng.rdetecte.bean;
2 |
3 | import java.util.ArrayList;
4 | import java.util.List;
5 |
6 | /**
7 | * 矢量图映射类,与xml结构对应
8 | * Created by HuWeiJian on 2017/12/28.
9 | */
10 |
11 | public class VectorPathInfo {
12 |
13 | private double viewportHeight;
14 | private double viewportWidth;
15 | private List paths=new ArrayList<>();
16 |
17 | public double getViewportHeight() {
18 | return viewportHeight;
19 | }
20 |
21 | public void setViewportHeight(double viewportHeight) {
22 | this.viewportHeight = viewportHeight;
23 | }
24 |
25 | public double getViewportWidth() {
26 | return viewportWidth;
27 | }
28 |
29 | public void setViewportWidth(double viewportWidth) {
30 | this.viewportWidth = viewportWidth;
31 | }
32 |
33 | public List getPaths() {
34 | return paths;
35 | }
36 |
37 | public void setPaths(List paths) {
38 | this.paths = paths;
39 | }
40 |
41 | public static class PathInfo{
42 | private String name;
43 | private String pathData;
44 |
45 | public String getName() {
46 | return name;
47 | }
48 |
49 | public void setName(String name) {
50 | this.name = name;
51 | }
52 |
53 | public String getPathData() {
54 | return pathData;
55 | }
56 |
57 | public void setPathData(String pathData) {
58 | this.pathData = pathData;
59 | }
60 | }
61 |
62 | }
63 |
--------------------------------------------------------------------------------
/lib_regiondetector/src/main/java/com/junmeng/rdetecte/utils/CommonUtil.java:
--------------------------------------------------------------------------------
1 | package com.junmeng.rdetecte.utils;
2 |
3 | import android.content.Context;
4 | import android.graphics.Bitmap;
5 | import android.graphics.Canvas;
6 | import android.graphics.Matrix;
7 | import android.graphics.Path;
8 | import android.graphics.PointF;
9 | import android.graphics.RectF;
10 | import android.graphics.Region;
11 | import android.graphics.drawable.Drawable;
12 | import android.os.Build;
13 | import android.support.v4.graphics.drawable.DrawableCompat;
14 | import android.support.v7.widget.AppCompatDrawableManager;
15 |
16 | import com.junmeng.rdetecte.bean.VectorPathInfo;
17 |
18 | import java.util.HashMap;
19 | import java.util.List;
20 | import java.util.Map;
21 |
22 | /**
23 | * Created by HWJ on 2017/2/19.
24 | */
25 |
26 | public class CommonUtil {
27 |
28 | /**
29 | * 根据路径生成Region
30 | *
31 | * @param path
32 | * @return
33 | */
34 | public static Region genRegion(Path path) {
35 | if(path==null){
36 | return null;
37 | }
38 | Region region = new Region();
39 | RectF r = new RectF();
40 | //得到Path的矩形边界
41 | path.computeBounds(r, true);
42 | // 设置区域路径和剪辑描述的区域
43 | region.setPath(path, new Region((int) (r.left), (int) (r.top),
44 | (int) (r.right),
45 | (int) (r.bottom)));
46 | return region;
47 | }
48 |
49 | /**
50 | * 一个坐标点,以某个点为缩放中心,缩放指定倍数,求这个坐标点在缩放后的新坐标值。
51 | *
52 | * @param targetPointX 坐标点的X
53 | * @param targetPointY 坐标点的Y
54 | * @param scaleCenterX 缩放中心的X
55 | * @param scaleCenterY 缩放中心的Y
56 | * @param scale 缩放倍数
57 | * @return 坐标点的新坐标
58 | */
59 | public static PointF scaleByPoint(float targetPointX, float targetPointY, float scaleCenterX, float scaleCenterY, float scale) {
60 | Matrix matrix = new Matrix();
61 | // 将Matrix移到到当前圆所在的位置,
62 | // 然后再以某个点为中心进行缩放
63 | matrix.preTranslate(targetPointX, targetPointY);
64 | matrix.postScale(scale, scale, scaleCenterX, scaleCenterY);
65 | float[] values = new float[9];
66 | matrix.getValues(values);
67 | return new PointF(values[Matrix.MTRANS_X], values[Matrix.MTRANS_Y]);
68 | }
69 |
70 |
71 | /**
72 | * 根据vector资源id获得Bitmap
73 | * 需要在build.gradle中配置
74 | * defaultConfig {
75 | * vectorDrawables.useSupportLibrary = true
76 | * }
77 | *
78 | * @param context
79 | * @param vectorDrawableId
80 | * @return
81 | */
82 | public static Bitmap getBitmapFromVectorDrawable(Context context, int vectorDrawableId) {
83 | Drawable drawable = AppCompatDrawableManager.get().getDrawable(context, vectorDrawableId);
84 | if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
85 | drawable = (DrawableCompat.wrap(drawable)).mutate();
86 | }
87 | Bitmap bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(),
88 | drawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
89 | Canvas canvas = new Canvas(bitmap);
90 | drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
91 | drawable.draw(canvas);
92 |
93 | return bitmap;
94 | }
95 |
96 | /**
97 | * 获得路径Map
98 | * @param paths
99 | * @return
100 | */
101 | public static Map getPaths(List paths){
102 | Map map=new HashMap<>();
103 | if(paths==null){
104 | return map;
105 | }
106 | for(VectorPathInfo.PathInfo info: paths){
107 | Path path= PathParser.createPathFromPathData(info.getPathData());
108 | if(path==null){
109 | path=new Path();
110 | }
111 | map.put(info.getName(),path);
112 | }
113 | return map;
114 | }
115 | }
116 |
--------------------------------------------------------------------------------
/lib_regiondetector/src/main/java/com/junmeng/rdetecte/utils/PathParser.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2017 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.junmeng.rdetecte.utils;
18 |
19 | import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
20 |
21 | import android.graphics.Path;
22 | import android.support.annotation.RestrictTo;
23 | import android.util.Log;
24 |
25 | import java.util.ArrayList;
26 |
27 | /**
28 | * This class is a duplicate from the PathParser.java of frameworks/base, with slight
29 | * update on incompatible API like copyOfRange().
30 | *
31 | * @hide
32 | */
33 | @RestrictTo(LIBRARY_GROUP)
34 | public class PathParser {
35 | private static final String LOGTAG = "PathParser";
36 |
37 | // Copy from Arrays.copyOfRange() which is only available from API level 9.
38 |
39 | /**
40 | * Copies elements from {@code original} into a new array, from indexes start (inclusive) to
41 | * end (exclusive). The original order of elements is preserved.
42 | * If {@code end} is greater than {@code original.length}, the result is padded
43 | * with the value {@code 0.0f}.
44 | *
45 | * @param original the original array
46 | * @param start the start index, inclusive
47 | * @param end the end index, exclusive
48 | * @return the new array
49 | * @throws ArrayIndexOutOfBoundsException if {@code start < 0 || start > original.length}
50 | * @throws IllegalArgumentException if {@code start > end}
51 | * @throws NullPointerException if {@code original == null}
52 | */
53 | static float[] copyOfRange(float[] original, int start, int end) {
54 | if (start > end) {
55 | throw new IllegalArgumentException();
56 | }
57 | int originalLength = original.length;
58 | if (start < 0 || start > originalLength) {
59 | throw new ArrayIndexOutOfBoundsException();
60 | }
61 | int resultLength = end - start;
62 | int copyLength = Math.min(resultLength, originalLength - start);
63 | float[] result = new float[resultLength];
64 | System.arraycopy(original, start, result, 0, copyLength);
65 | return result;
66 | }
67 |
68 | /**
69 | * @param pathData The string representing a path, the same as "d" string in svg file.
70 | * @return the generated Path object.
71 | */
72 | public static Path createPathFromPathData(String pathData) {
73 | Path path = new Path();
74 | PathDataNode[] nodes = createNodesFromPathData(pathData);
75 | if (nodes != null) {
76 | try {
77 | PathDataNode.nodesToPath(nodes, path);
78 | } catch (RuntimeException e) {
79 | throw new RuntimeException("Error in parsing " + pathData, e);
80 | }
81 | return path;
82 | }
83 | return null;
84 | }
85 |
86 | /**
87 | * @param pathData The string representing a path, the same as "d" string in svg file.
88 | * @return an array of the PathDataNode.
89 | */
90 | public static PathDataNode[] createNodesFromPathData(String pathData) {
91 | if (pathData == null) {
92 | return null;
93 | }
94 | int start = 0;
95 | int end = 1;
96 |
97 | ArrayList list = new ArrayList();
98 | while (end < pathData.length()) {
99 | end = nextStart(pathData, end);
100 | String s = pathData.substring(start, end).trim();
101 | if (s.length() > 0) {
102 | float[] val = getFloats(s);
103 | addNode(list, s.charAt(0), val);
104 | }
105 |
106 | start = end;
107 | end++;
108 | }
109 | if ((end - start) == 1 && start < pathData.length()) {
110 | addNode(list, pathData.charAt(start), new float[0]);
111 | }
112 | return list.toArray(new PathDataNode[list.size()]);
113 | }
114 |
115 | /**
116 | * @param source The array of PathDataNode to be duplicated.
117 | * @return a deep copy of the source
.
118 | */
119 | public static PathDataNode[] deepCopyNodes(PathDataNode[] source) {
120 | if (source == null) {
121 | return null;
122 | }
123 | PathDataNode[] copy = new PathParser.PathDataNode[source.length];
124 | for (int i = 0; i < source.length; i++) {
125 | copy[i] = new PathDataNode(source[i]);
126 | }
127 | return copy;
128 | }
129 |
130 | /**
131 | * @param nodesFrom The source path represented in an array of PathDataNode
132 | * @param nodesTo The target path represented in an array of PathDataNode
133 | * @return whether the nodesFrom
can morph into nodesTo
134 | */
135 | public static boolean canMorph(PathDataNode[] nodesFrom, PathDataNode[] nodesTo) {
136 | if (nodesFrom == null || nodesTo == null) {
137 | return false;
138 | }
139 |
140 | if (nodesFrom.length != nodesTo.length) {
141 | return false;
142 | }
143 |
144 | for (int i = 0; i < nodesFrom.length; i++) {
145 | if (nodesFrom[i].mType != nodesTo[i].mType
146 | || nodesFrom[i].mParams.length != nodesTo[i].mParams.length) {
147 | return false;
148 | }
149 | }
150 | return true;
151 | }
152 |
153 | /**
154 | * Update the target's data to match the source.
155 | * Before calling this, make sure canMorph(target, source) is true.
156 | *
157 | * @param target The target path represented in an array of PathDataNode
158 | * @param source The source path represented in an array of PathDataNode
159 | */
160 | public static void updateNodes(PathDataNode[] target, PathDataNode[] source) {
161 | for (int i = 0; i < source.length; i++) {
162 | target[i].mType = source[i].mType;
163 | for (int j = 0; j < source[i].mParams.length; j++) {
164 | target[i].mParams[j] = source[i].mParams[j];
165 | }
166 | }
167 | }
168 |
169 | private static int nextStart(String s, int end) {
170 | char c;
171 |
172 | while (end < s.length()) {
173 | c = s.charAt(end);
174 | // Note that 'e' or 'E' are not valid path commands, but could be
175 | // used for floating point numbers' scientific notation.
176 | // Therefore, when searching for next command, we should ignore 'e'
177 | // and 'E'.
178 | if ((((c - 'A') * (c - 'Z') <= 0) || ((c - 'a') * (c - 'z') <= 0))
179 | && c != 'e' && c != 'E') {
180 | return end;
181 | }
182 | end++;
183 | }
184 | return end;
185 | }
186 |
187 | private static void addNode(ArrayList list, char cmd, float[] val) {
188 | list.add(new PathDataNode(cmd, val));
189 | }
190 |
191 | private static class ExtractFloatResult {
192 | // We need to return the position of the next separator and whether the
193 | // next float starts with a '-' or a '.'.
194 | int mEndPosition;
195 | boolean mEndWithNegOrDot;
196 |
197 | ExtractFloatResult() {
198 | }
199 | }
200 |
201 | /**
202 | * Parse the floats in the string.
203 | * This is an optimized version of parseFloat(s.split(",|\\s"));
204 | *
205 | * @param s the string containing a command and list of floats
206 | * @return array of floats
207 | */
208 | private static float[] getFloats(String s) {
209 | if (s.charAt(0) == 'z' || s.charAt(0) == 'Z') {
210 | return new float[0];
211 | }
212 | try {
213 | float[] results = new float[s.length()];
214 | int count = 0;
215 | int startPosition = 1;
216 | int endPosition = 0;
217 |
218 | ExtractFloatResult result = new ExtractFloatResult();
219 | int totalLength = s.length();
220 |
221 | // The startPosition should always be the first character of the
222 | // current number, and endPosition is the character after the current
223 | // number.
224 | while (startPosition < totalLength) {
225 | extract(s, startPosition, result);
226 | endPosition = result.mEndPosition;
227 |
228 | if (startPosition < endPosition) {
229 | results[count++] = Float.parseFloat(
230 | s.substring(startPosition, endPosition));
231 | }
232 |
233 | if (result.mEndWithNegOrDot) {
234 | // Keep the '-' or '.' sign with next number.
235 | startPosition = endPosition;
236 | } else {
237 | startPosition = endPosition + 1;
238 | }
239 | }
240 | return copyOfRange(results, 0, count);
241 | } catch (NumberFormatException e) {
242 | throw new RuntimeException("error in parsing \"" + s + "\"", e);
243 | }
244 | }
245 |
246 | /**
247 | * Calculate the position of the next comma or space or negative sign
248 | *
249 | * @param s the string to search
250 | * @param start the position to start searching
251 | * @param result the result of the extraction, including the position of the
252 | * the starting position of next number, whether it is ending with a '-'.
253 | */
254 | private static void extract(String s, int start, ExtractFloatResult result) {
255 | // Now looking for ' ', ',', '.' or '-' from the start.
256 | int currentIndex = start;
257 | boolean foundSeparator = false;
258 | result.mEndWithNegOrDot = false;
259 | boolean secondDot = false;
260 | boolean isExponential = false;
261 | for (; currentIndex < s.length(); currentIndex++) {
262 | boolean isPrevExponential = isExponential;
263 | isExponential = false;
264 | char currentChar = s.charAt(currentIndex);
265 | switch (currentChar) {
266 | case ' ':
267 | case ',':
268 | foundSeparator = true;
269 | break;
270 | case '-':
271 | // The negative sign following a 'e' or 'E' is not a separator.
272 | if (currentIndex != start && !isPrevExponential) {
273 | foundSeparator = true;
274 | result.mEndWithNegOrDot = true;
275 | }
276 | break;
277 | case '.':
278 | if (!secondDot) {
279 | secondDot = true;
280 | } else {
281 | // This is the second dot, and it is considered as a separator.
282 | foundSeparator = true;
283 | result.mEndWithNegOrDot = true;
284 | }
285 | break;
286 | case 'e':
287 | case 'E':
288 | isExponential = true;
289 | break;
290 | }
291 | if (foundSeparator) {
292 | break;
293 | }
294 | }
295 | // When there is nothing found, then we put the end position to the end
296 | // of the string.
297 | result.mEndPosition = currentIndex;
298 | }
299 |
300 | /**
301 | * Each PathDataNode represents one command in the "d" attribute of the svg
302 | * file.
303 | * An array of PathDataNode can represent the whole "d" attribute.
304 | */
305 | public static class PathDataNode {
306 |
307 | /**
308 | * @hide
309 | */
310 | @RestrictTo(LIBRARY_GROUP)
311 | public char mType;
312 |
313 | /**
314 | * @hide
315 | */
316 | @RestrictTo(LIBRARY_GROUP)
317 | public float[] mParams;
318 |
319 | PathDataNode(char type, float[] params) {
320 | this.mType = type;
321 | this.mParams = params;
322 | }
323 |
324 | PathDataNode(PathDataNode n) {
325 | mType = n.mType;
326 | mParams = copyOfRange(n.mParams, 0, n.mParams.length);
327 | }
328 |
329 | /**
330 | * Convert an array of PathDataNode to Path.
331 | *
332 | * @param node The source array of PathDataNode.
333 | * @param path The target Path object.
334 | */
335 | public static void nodesToPath(PathDataNode[] node, Path path) {
336 | float[] current = new float[6];
337 | char previousCommand = 'm';
338 | for (int i = 0; i < node.length; i++) {
339 | addCommand(path, current, previousCommand, node[i].mType, node[i].mParams);
340 | previousCommand = node[i].mType;
341 | }
342 | }
343 |
344 | /**
345 | * The current PathDataNode will be interpolated between the
346 | * nodeFrom
and nodeTo
according to the
347 | * fraction
.
348 | *
349 | * @param nodeFrom The start value as a PathDataNode.
350 | * @param nodeTo The end value as a PathDataNode
351 | * @param fraction The fraction to interpolate.
352 | */
353 | public void interpolatePathDataNode(PathDataNode nodeFrom,
354 | PathDataNode nodeTo, float fraction) {
355 | for (int i = 0; i < nodeFrom.mParams.length; i++) {
356 | mParams[i] = nodeFrom.mParams[i] * (1 - fraction)
357 | + nodeTo.mParams[i] * fraction;
358 | }
359 | }
360 |
361 | private static void addCommand(Path path, float[] current,
362 | char previousCmd, char cmd, float[] val) {
363 |
364 | int incr = 2;
365 | float currentX = current[0];
366 | float currentY = current[1];
367 | float ctrlPointX = current[2];
368 | float ctrlPointY = current[3];
369 | float currentSegmentStartX = current[4];
370 | float currentSegmentStartY = current[5];
371 | float reflectiveCtrlPointX;
372 | float reflectiveCtrlPointY;
373 |
374 | switch (cmd) {
375 | case 'z':
376 | case 'Z':
377 | path.close();
378 | // Path is closed here, but we need to move the pen to the
379 | // closed position. So we cache the segment's starting position,
380 | // and restore it here.
381 | currentX = currentSegmentStartX;
382 | currentY = currentSegmentStartY;
383 | ctrlPointX = currentSegmentStartX;
384 | ctrlPointY = currentSegmentStartY;
385 | path.moveTo(currentX, currentY);
386 | break;
387 | case 'm':
388 | case 'M':
389 | case 'l':
390 | case 'L':
391 | case 't':
392 | case 'T':
393 | incr = 2;
394 | break;
395 | case 'h':
396 | case 'H':
397 | case 'v':
398 | case 'V':
399 | incr = 1;
400 | break;
401 | case 'c':
402 | case 'C':
403 | incr = 6;
404 | break;
405 | case 's':
406 | case 'S':
407 | case 'q':
408 | case 'Q':
409 | incr = 4;
410 | break;
411 | case 'a':
412 | case 'A':
413 | incr = 7;
414 | break;
415 | }
416 |
417 | for (int k = 0; k < val.length; k += incr) {
418 | switch (cmd) {
419 | case 'm': // moveto - Start a new sub-path (relative)
420 | currentX += val[k + 0];
421 | currentY += val[k + 1];
422 | if (k > 0) {
423 | // According to the spec, if a moveto is followed by multiple
424 | // pairs of coordinates, the subsequent pairs are treated as
425 | // implicit lineto commands.
426 | path.rLineTo(val[k + 0], val[k + 1]);
427 | } else {
428 | path.rMoveTo(val[k + 0], val[k + 1]);
429 | currentSegmentStartX = currentX;
430 | currentSegmentStartY = currentY;
431 | }
432 | break;
433 | case 'M': // moveto - Start a new sub-path
434 | currentX = val[k + 0];
435 | currentY = val[k + 1];
436 | if (k > 0) {
437 | // According to the spec, if a moveto is followed by multiple
438 | // pairs of coordinates, the subsequent pairs are treated as
439 | // implicit lineto commands.
440 | path.lineTo(val[k + 0], val[k + 1]);
441 | } else {
442 | path.moveTo(val[k + 0], val[k + 1]);
443 | currentSegmentStartX = currentX;
444 | currentSegmentStartY = currentY;
445 | }
446 | break;
447 | case 'l': // lineto - Draw a line from the current point (relative)
448 | path.rLineTo(val[k + 0], val[k + 1]);
449 | currentX += val[k + 0];
450 | currentY += val[k + 1];
451 | break;
452 | case 'L': // lineto - Draw a line from the current point
453 | path.lineTo(val[k + 0], val[k + 1]);
454 | currentX = val[k + 0];
455 | currentY = val[k + 1];
456 | break;
457 | case 'h': // horizontal lineto - Draws a horizontal line (relative)
458 | path.rLineTo(val[k + 0], 0);
459 | currentX += val[k + 0];
460 | break;
461 | case 'H': // horizontal lineto - Draws a horizontal line
462 | path.lineTo(val[k + 0], currentY);
463 | currentX = val[k + 0];
464 | break;
465 | case 'v': // vertical lineto - Draws a vertical line from the current point (r)
466 | path.rLineTo(0, val[k + 0]);
467 | currentY += val[k + 0];
468 | break;
469 | case 'V': // vertical lineto - Draws a vertical line from the current point
470 | path.lineTo(currentX, val[k + 0]);
471 | currentY = val[k + 0];
472 | break;
473 | case 'c': // curveto - Draws a cubic Bézier curve (relative)
474 | path.rCubicTo(val[k + 0], val[k + 1], val[k + 2], val[k + 3],
475 | val[k + 4], val[k + 5]);
476 |
477 | ctrlPointX = currentX + val[k + 2];
478 | ctrlPointY = currentY + val[k + 3];
479 | currentX += val[k + 4];
480 | currentY += val[k + 5];
481 |
482 | break;
483 | case 'C': // curveto - Draws a cubic Bézier curve
484 | path.cubicTo(val[k + 0], val[k + 1], val[k + 2], val[k + 3],
485 | val[k + 4], val[k + 5]);
486 | currentX = val[k + 4];
487 | currentY = val[k + 5];
488 | ctrlPointX = val[k + 2];
489 | ctrlPointY = val[k + 3];
490 | break;
491 | case 's': // smooth curveto - Draws a cubic Bézier curve (reflective cp)
492 | reflectiveCtrlPointX = 0;
493 | reflectiveCtrlPointY = 0;
494 | if (previousCmd == 'c' || previousCmd == 's'
495 | || previousCmd == 'C' || previousCmd == 'S') {
496 | reflectiveCtrlPointX = currentX - ctrlPointX;
497 | reflectiveCtrlPointY = currentY - ctrlPointY;
498 | }
499 | path.rCubicTo(reflectiveCtrlPointX, reflectiveCtrlPointY,
500 | val[k + 0], val[k + 1],
501 | val[k + 2], val[k + 3]);
502 |
503 | ctrlPointX = currentX + val[k + 0];
504 | ctrlPointY = currentY + val[k + 1];
505 | currentX += val[k + 2];
506 | currentY += val[k + 3];
507 | break;
508 | case 'S': // shorthand/smooth curveto Draws a cubic Bézier curve(reflective cp)
509 | reflectiveCtrlPointX = currentX;
510 | reflectiveCtrlPointY = currentY;
511 | if (previousCmd == 'c' || previousCmd == 's'
512 | || previousCmd == 'C' || previousCmd == 'S') {
513 | reflectiveCtrlPointX = 2 * currentX - ctrlPointX;
514 | reflectiveCtrlPointY = 2 * currentY - ctrlPointY;
515 | }
516 | path.cubicTo(reflectiveCtrlPointX, reflectiveCtrlPointY,
517 | val[k + 0], val[k + 1], val[k + 2], val[k + 3]);
518 | ctrlPointX = val[k + 0];
519 | ctrlPointY = val[k + 1];
520 | currentX = val[k + 2];
521 | currentY = val[k + 3];
522 | break;
523 | case 'q': // Draws a quadratic Bézier (relative)
524 | path.rQuadTo(val[k + 0], val[k + 1], val[k + 2], val[k + 3]);
525 | ctrlPointX = currentX + val[k + 0];
526 | ctrlPointY = currentY + val[k + 1];
527 | currentX += val[k + 2];
528 | currentY += val[k + 3];
529 | break;
530 | case 'Q': // Draws a quadratic Bézier
531 | path.quadTo(val[k + 0], val[k + 1], val[k + 2], val[k + 3]);
532 | ctrlPointX = val[k + 0];
533 | ctrlPointY = val[k + 1];
534 | currentX = val[k + 2];
535 | currentY = val[k + 3];
536 | break;
537 | case 't': // Draws a quadratic Bézier curve(reflective control point)(relative)
538 | reflectiveCtrlPointX = 0;
539 | reflectiveCtrlPointY = 0;
540 | if (previousCmd == 'q' || previousCmd == 't'
541 | || previousCmd == 'Q' || previousCmd == 'T') {
542 | reflectiveCtrlPointX = currentX - ctrlPointX;
543 | reflectiveCtrlPointY = currentY - ctrlPointY;
544 | }
545 | path.rQuadTo(reflectiveCtrlPointX, reflectiveCtrlPointY,
546 | val[k + 0], val[k + 1]);
547 | ctrlPointX = currentX + reflectiveCtrlPointX;
548 | ctrlPointY = currentY + reflectiveCtrlPointY;
549 | currentX += val[k + 0];
550 | currentY += val[k + 1];
551 | break;
552 | case 'T': // Draws a quadratic Bézier curve (reflective control point)
553 | reflectiveCtrlPointX = currentX;
554 | reflectiveCtrlPointY = currentY;
555 | if (previousCmd == 'q' || previousCmd == 't'
556 | || previousCmd == 'Q' || previousCmd == 'T') {
557 | reflectiveCtrlPointX = 2 * currentX - ctrlPointX;
558 | reflectiveCtrlPointY = 2 * currentY - ctrlPointY;
559 | }
560 | path.quadTo(reflectiveCtrlPointX, reflectiveCtrlPointY,
561 | val[k + 0], val[k + 1]);
562 | ctrlPointX = reflectiveCtrlPointX;
563 | ctrlPointY = reflectiveCtrlPointY;
564 | currentX = val[k + 0];
565 | currentY = val[k + 1];
566 | break;
567 | case 'a': // Draws an elliptical arc
568 | // (rx ry x-axis-rotation large-arc-flag sweep-flag x y)
569 | drawArc(path,
570 | currentX,
571 | currentY,
572 | val[k + 5] + currentX,
573 | val[k + 6] + currentY,
574 | val[k + 0],
575 | val[k + 1],
576 | val[k + 2],
577 | val[k + 3] != 0,
578 | val[k + 4] != 0);
579 | currentX += val[k + 5];
580 | currentY += val[k + 6];
581 | ctrlPointX = currentX;
582 | ctrlPointY = currentY;
583 | break;
584 | case 'A': // Draws an elliptical arc
585 | drawArc(path,
586 | currentX,
587 | currentY,
588 | val[k + 5],
589 | val[k + 6],
590 | val[k + 0],
591 | val[k + 1],
592 | val[k + 2],
593 | val[k + 3] != 0,
594 | val[k + 4] != 0);
595 | currentX = val[k + 5];
596 | currentY = val[k + 6];
597 | ctrlPointX = currentX;
598 | ctrlPointY = currentY;
599 | break;
600 | }
601 | previousCmd = cmd;
602 | }
603 | current[0] = currentX;
604 | current[1] = currentY;
605 | current[2] = ctrlPointX;
606 | current[3] = ctrlPointY;
607 | current[4] = currentSegmentStartX;
608 | current[5] = currentSegmentStartY;
609 | }
610 |
611 | private static void drawArc(Path p,
612 | float x0,
613 | float y0,
614 | float x1,
615 | float y1,
616 | float a,
617 | float b,
618 | float theta,
619 | boolean isMoreThanHalf,
620 | boolean isPositiveArc) {
621 |
622 | /* Convert rotation angle from degrees to radians */
623 | double thetaD = Math.toRadians(theta);
624 | /* Pre-compute rotation matrix entries */
625 | double cosTheta = Math.cos(thetaD);
626 | double sinTheta = Math.sin(thetaD);
627 | /* Transform (x0, y0) and (x1, y1) into unit space */
628 | /* using (inverse) rotation, followed by (inverse) scale */
629 | double x0p = (x0 * cosTheta + y0 * sinTheta) / a;
630 | double y0p = (-x0 * sinTheta + y0 * cosTheta) / b;
631 | double x1p = (x1 * cosTheta + y1 * sinTheta) / a;
632 | double y1p = (-x1 * sinTheta + y1 * cosTheta) / b;
633 |
634 | /* Compute differences and averages */
635 | double dx = x0p - x1p;
636 | double dy = y0p - y1p;
637 | double xm = (x0p + x1p) / 2;
638 | double ym = (y0p + y1p) / 2;
639 | /* Solve for intersecting unit circles */
640 | double dsq = dx * dx + dy * dy;
641 | if (dsq == 0.0) {
642 | Log.w(LOGTAG, " Points are coincident");
643 | return; /* Points are coincident */
644 | }
645 | double disc = 1.0 / dsq - 1.0 / 4.0;
646 | if (disc < 0.0) {
647 | Log.w(LOGTAG, "Points are too far apart " + dsq);
648 | float adjust = (float) (Math.sqrt(dsq) / 1.99999);
649 | drawArc(p, x0, y0, x1, y1, a * adjust,
650 | b * adjust, theta, isMoreThanHalf, isPositiveArc);
651 | return; /* Points are too far apart */
652 | }
653 | double s = Math.sqrt(disc);
654 | double sdx = s * dx;
655 | double sdy = s * dy;
656 | double cx;
657 | double cy;
658 | if (isMoreThanHalf == isPositiveArc) {
659 | cx = xm - sdy;
660 | cy = ym + sdx;
661 | } else {
662 | cx = xm + sdy;
663 | cy = ym - sdx;
664 | }
665 |
666 | double eta0 = Math.atan2((y0p - cy), (x0p - cx));
667 |
668 | double eta1 = Math.atan2((y1p - cy), (x1p - cx));
669 |
670 | double sweep = (eta1 - eta0);
671 | if (isPositiveArc != (sweep >= 0)) {
672 | if (sweep > 0) {
673 | sweep -= 2 * Math.PI;
674 | } else {
675 | sweep += 2 * Math.PI;
676 | }
677 | }
678 |
679 | cx *= a;
680 | cy *= b;
681 | double tcx = cx;
682 | cx = cx * cosTheta - cy * sinTheta;
683 | cy = tcx * sinTheta + cy * cosTheta;
684 |
685 | arcToBezier(p, cx, cy, a, b, x0, y0, thetaD, eta0, sweep);
686 | }
687 |
688 | /**
689 | * Converts an arc to cubic Bezier segments and records them in p.
690 | *
691 | * @param p The target for the cubic Bezier segments
692 | * @param cx The x coordinate center of the ellipse
693 | * @param cy The y coordinate center of the ellipse
694 | * @param a The radius of the ellipse in the horizontal direction
695 | * @param b The radius of the ellipse in the vertical direction
696 | * @param e1x E(eta1) x coordinate of the starting point of the arc
697 | * @param e1y E(eta2) y coordinate of the starting point of the arc
698 | * @param theta The angle that the ellipse bounding rectangle makes with horizontal plane
699 | * @param start The start angle of the arc on the ellipse
700 | * @param sweep The angle (positive or negative) of the sweep of the arc on the ellipse
701 | */
702 | private static void arcToBezier(Path p,
703 | double cx,
704 | double cy,
705 | double a,
706 | double b,
707 | double e1x,
708 | double e1y,
709 | double theta,
710 | double start,
711 | double sweep) {
712 | // Taken from equations at: http://spaceroots.org/documents/ellipse/node8.html
713 | // and http://www.spaceroots.org/documents/ellipse/node22.html
714 |
715 | // Maximum of 45 degrees per cubic Bezier segment
716 | int numSegments = (int) Math.ceil(Math.abs(sweep * 4 / Math.PI));
717 |
718 | double eta1 = start;
719 | double cosTheta = Math.cos(theta);
720 | double sinTheta = Math.sin(theta);
721 | double cosEta1 = Math.cos(eta1);
722 | double sinEta1 = Math.sin(eta1);
723 | double ep1x = (-a * cosTheta * sinEta1) - (b * sinTheta * cosEta1);
724 | double ep1y = (-a * sinTheta * sinEta1) + (b * cosTheta * cosEta1);
725 |
726 | double anglePerSegment = sweep / numSegments;
727 | for (int i = 0; i < numSegments; i++) {
728 | double eta2 = eta1 + anglePerSegment;
729 | double sinEta2 = Math.sin(eta2);
730 | double cosEta2 = Math.cos(eta2);
731 | double e2x = cx + (a * cosTheta * cosEta2) - (b * sinTheta * sinEta2);
732 | double e2y = cy + (a * sinTheta * cosEta2) + (b * cosTheta * sinEta2);
733 | double ep2x = -a * cosTheta * sinEta2 - b * sinTheta * cosEta2;
734 | double ep2y = -a * sinTheta * sinEta2 + b * cosTheta * cosEta2;
735 | double tanDiff2 = Math.tan((eta2 - eta1) / 2);
736 | double alpha =
737 | Math.sin(eta2 - eta1) * (Math.sqrt(4 + (3 * tanDiff2 * tanDiff2)) - 1) / 3;
738 | double q1x = e1x + alpha * ep1x;
739 | double q1y = e1y + alpha * ep1y;
740 | double q2x = e2x - alpha * ep2x;
741 | double q2y = e2y - alpha * ep2y;
742 |
743 | // Adding this no-op call to workaround a proguard related issue.
744 | p.rLineTo(0, 0);
745 |
746 | p.cubicTo((float) q1x,
747 | (float) q1y,
748 | (float) q2x,
749 | (float) q2y,
750 | (float) e2x,
751 | (float) e2y);
752 | eta1 = eta2;
753 | e1x = e2x;
754 | e1y = e2y;
755 | ep1x = ep2x;
756 | ep1y = ep2y;
757 | }
758 | }
759 | }
760 | }
761 |
--------------------------------------------------------------------------------
/lib_regiondetector/src/main/java/com/junmeng/rdetecte/utils/VectorMapParser.java:
--------------------------------------------------------------------------------
1 | package com.junmeng.rdetecte.utils;
2 |
3 | import android.annotation.SuppressLint;
4 | import android.content.res.Resources;
5 | import android.support.annotation.DrawableRes;
6 | import android.support.annotation.NonNull;
7 | import android.util.Log;
8 |
9 | import com.junmeng.rdetecte.bean.VectorPathInfo;
10 |
11 | import org.xmlpull.v1.XmlPullParser;
12 |
13 | /**
14 | * vector矢量图解析器
15 | * Created by HuWeiJian on 2017/12/27.
16 | */
17 |
18 | public class VectorMapParser {
19 | private static final String TAG = "VectorMapParser";
20 | public static final String NAMESPACE = "http://schemas.android.com/apk/res/android";
21 |
22 | public VectorMapParser() {
23 |
24 | }
25 |
26 |
27 | public VectorPathInfo parse(@NonNull Resources res, @DrawableRes int resId) {
28 | VectorPathInfo vectorPathInfo = new VectorPathInfo();
29 | try {
30 | @SuppressLint("ResourceType") final XmlPullParser parser = res.getXml(resId);
31 |
32 | int event = parser.getEventType();// 触发第一個事件;
33 | String tag;
34 | while (event != XmlPullParser.END_DOCUMENT) {
35 | tag = parser.getName();
36 | switch (event) {
37 | case XmlPullParser.START_DOCUMENT:
38 | Log.w(TAG, "parse:START_DOCUMENT " + parser.getName());
39 | break;
40 | case XmlPullParser.START_TAG:
41 | //parser.getAttributeName(0)=name
42 | //parser.getAttributeNamespace(0)= http://schemas.android.com/apk/res/android
43 | Log.w(TAG, "parse:START_TAG " + parser.getName());
44 | if ("vector".equals(tag)) {
45 | vectorPathInfo.setViewportWidth(Double.parseDouble(parser.getAttributeValue(NAMESPACE, "viewportWidth")));
46 | vectorPathInfo.setViewportHeight(Double.parseDouble(parser.getAttributeValue(NAMESPACE, "viewportHeight")));
47 | } else if ("path".equals(tag)) {
48 | VectorPathInfo.PathInfo pathInfo = new VectorPathInfo.PathInfo();
49 | pathInfo.setName(res.getString(Integer.parseInt(parser.getAttributeValue(NAMESPACE, "name").replace("@", ""))));
50 | pathInfo.setPathData(parser.getAttributeValue(NAMESPACE, "pathData"));
51 | vectorPathInfo.getPaths().add(pathInfo);
52 | }
53 | break;
54 | case XmlPullParser.END_TAG:
55 | Log.w(TAG, "parse:END_TAG " + parser.getName());
56 | break;
57 | }
58 | event = parser.next();
59 | }
60 | } catch (Exception e) {
61 | e.printStackTrace();
62 | }
63 |
64 | Log.w(TAG, "parse: " + vectorPathInfo.getPaths().size());
65 | return vectorPathInfo;
66 | }
67 |
68 |
69 | }
70 |
--------------------------------------------------------------------------------
/lib_regiondetector/src/main/java/com/junmeng/rdetecte/widget/BaseSurfaceView.java:
--------------------------------------------------------------------------------
1 | package com.junmeng.rdetecte.widget;
2 |
3 | import android.content.Context;
4 | import android.graphics.Bitmap;
5 | import android.graphics.Canvas;
6 | import android.graphics.Color;
7 | import android.graphics.Paint;
8 | import android.graphics.PorterDuff;
9 | import android.support.annotation.ColorInt;
10 | import android.util.AttributeSet;
11 | import android.util.TypedValue;
12 | import android.view.SurfaceHolder;
13 | import android.view.SurfaceView;
14 |
15 | /**
16 | * 封装了绘图线程和背景等的设置
17 | * Created by HWJ on 2016/12/10.
18 | */
19 |
20 | public abstract class BaseSurfaceView extends SurfaceView implements SurfaceHolder.Callback2 {
21 | private static final String TAG = "BaseSurfaceView";
22 | protected DrawThread drawThread;
23 |
24 | public Paint paint = new Paint();
25 | public int paintColor = 0xffff0000;//画笔的颜色
26 | public int paintStrokeWidth = 2;
27 |
28 | public int screenWidth;//surfaveview宽高
29 | public int screenHeight;
30 | public float screenCenterX;//surfaceView的中心点x坐标
31 | public float screenCenterY;//surfaceView的中心点y坐标
32 |
33 | protected int drawPauseTime = 30;//一次绘制后休息50ms
34 |
35 | public Bitmap bgBitmap = null;//背景图片
36 | public int bgColor = Color.TRANSPARENT;//背景颜色
37 | public int lineColor = 0xff00ff00;//线的颜色
38 | public int pointColor = 0xff0000ff;//点的颜色
39 | public int pointSize = 5;//点的大小
40 |
41 |
42 | public BaseSurfaceView(Context context, AttributeSet attrs, int defStyleAttr) {
43 | super(context, attrs, defStyleAttr);
44 | initBaseSurfaceView();
45 | }
46 |
47 | public BaseSurfaceView(Context context) {
48 | this(context, null, 0);
49 | }
50 |
51 | public BaseSurfaceView(Context context, AttributeSet attrs) {
52 | super(context, attrs, 0);
53 | }
54 |
55 | private void initBaseSurfaceView() {
56 | //设置画笔
57 | paint.setColor(paintColor);
58 | paint.setStrokeWidth(paintStrokeWidth);
59 | paint.setStyle(Paint.Style.STROKE);
60 | paint.setAntiAlias(true);
61 | paint.setTextSize(20);
62 |
63 |
64 | //必须设置此处,否则获取不到touch事件
65 | setFocusable(true);
66 | setFocusableInTouchMode(true);
67 | getHolder().addCallback(this);
68 | //drawThread = new DrawThread(getHolder());
69 | }
70 |
71 | public void setLineColor(@ColorInt int color) {
72 | this.lineColor = color;
73 | }
74 |
75 |
76 | public void setPointColor(@ColorInt int color) {
77 | this.pointColor = color;
78 | }
79 |
80 | /**
81 | * 设置笔刷的粗细
82 | *
83 | * @param px
84 | */
85 | public void setPaintStrokeWidth(int px) {
86 | paintStrokeWidth = px;
87 | paint.setStrokeWidth(paintStrokeWidth);
88 | }
89 |
90 | /**
91 | * 设置点的大小
92 | *
93 | * @param radius 半径
94 | */
95 | public void setPointSize(int radius) {
96 | this.pointSize = radius;
97 | }
98 |
99 | /**
100 | * 设置背景颜色
101 | *
102 | * @param color
103 | */
104 | public void setBackgroundColor(@ColorInt int color) {
105 | this.bgColor = color;
106 | }
107 |
108 | /**
109 | * 设置背景
110 | *
111 | * @param bg
112 | */
113 | public void setBackgroundBitmap(Bitmap bg) {
114 | bgBitmap = bg;
115 | }
116 |
117 |
118 | /**
119 | * 设置一次绘制后休息时间,默认50ms
120 | *
121 | * @param time ms
122 | */
123 | public void setDrawPauseTime(int time) {
124 | drawPauseTime = time;
125 | }
126 |
127 | @Override
128 | public void surfaceRedrawNeeded(SurfaceHolder surfaceHolder) {
129 |
130 | }
131 |
132 | @Override
133 | public void surfaceCreated(SurfaceHolder surfaceHolder) {
134 | /*Canvas canvas = surfaceHolder.lockCanvas();
135 | if(canvas != null) {
136 | canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
137 | surfaceHolder.unlockCanvasAndPost(canvas);
138 | }*/
139 | drawThread = new DrawThread(surfaceHolder);
140 | drawThread.start();
141 | }
142 |
143 | @Override
144 | public void surfaceChanged(SurfaceHolder surfaceHolder, int i, int i1, int i2) {
145 | screenWidth = i1;
146 | screenHeight = i2;
147 | screenCenterX = screenWidth / 2.0f;
148 | screenCenterY = screenHeight / 2.0f;
149 |
150 | }
151 |
152 | @Override
153 | public void surfaceDestroyed(SurfaceHolder surfaceHolder) {
154 | drawThread.stopDraw();
155 | }
156 |
157 | /**
158 | * 在此处执行绘制过程
159 | *
160 | * @param c
161 | */
162 | public abstract void doDraw(Canvas c);
163 |
164 | /**
165 | * 绘制背景
166 | *
167 | * @param canvas
168 | */
169 | private void drawBackground(Canvas canvas) {
170 | if (bgColor == 0) {
171 | canvas.drawColor(bgColor, PorterDuff.Mode.CLEAR);
172 | } else {
173 | canvas.drawColor(bgColor);
174 | }
175 |
176 | if (bgBitmap != null) {
177 | try {
178 | canvas.drawBitmap(Bitmap.createScaledBitmap(bgBitmap, getMeasuredWidth(), getMeasuredHeight(), true), 0, 0, null);
179 | } catch (Exception e) {
180 | e.printStackTrace();
181 | }
182 | }
183 | }
184 |
185 | public class DrawThread extends Thread {
186 | SurfaceHolder surfaceHolder;
187 |
188 | boolean isRunning = true;
189 |
190 | public DrawThread(SurfaceHolder surfaceHolder) {
191 | this.surfaceHolder = surfaceHolder;
192 | }
193 |
194 | @Override
195 | public void run() {
196 |
197 | Canvas canvas = null;
198 | while (isRunning) {
199 | try {
200 | canvas = surfaceHolder.lockCanvas();
201 | if (canvas != null) {
202 | drawBackground(canvas);
203 | doDraw(canvas);
204 | }
205 | if (drawPauseTime > 0) {
206 | Thread.sleep(drawPauseTime);//通过它来控制帧数执行一次绘制后休息50ms)
207 | }else{
208 | Thread.sleep(5);//休眠一小段时间避免while循环引发CPU被占满
209 | }
210 | } catch (InterruptedException e) {
211 | e.printStackTrace();
212 | } finally {
213 | if (canvas != null) {
214 | surfaceHolder.unlockCanvasAndPost(canvas);
215 |
216 | }
217 | }
218 | }
219 |
220 | }
221 |
222 | /**
223 | * 停止绘制
224 | */
225 | public void stopDraw() {
226 | isRunning = false;
227 | }
228 | }
229 |
230 | public int dp2px(float dpVal) {
231 | return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dpVal, getResources().getDisplayMetrics());
232 | }
233 |
234 | public int sp2px(float spVal) {
235 | return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, spVal, getResources().getDisplayMetrics());
236 | }
237 | }
--------------------------------------------------------------------------------
/lib_regiondetector/src/main/java/com/junmeng/rdetecte/widget/RegionDetectSurfaceView.java:
--------------------------------------------------------------------------------
1 | package com.junmeng.rdetecte.widget;
2 |
3 | import android.animation.ValueAnimator;
4 | import android.content.Context;
5 | import android.graphics.Bitmap;
6 | import android.graphics.Canvas;
7 | import android.graphics.Color;
8 | import android.graphics.Matrix;
9 | import android.graphics.Paint;
10 | import android.graphics.Path;
11 | import android.graphics.PointF;
12 | import android.graphics.Rect;
13 | import android.graphics.Region;
14 | import android.support.annotation.ColorInt;
15 | import android.support.annotation.DrawableRes;
16 | import android.support.annotation.IntDef;
17 | import android.support.annotation.NonNull;
18 | import android.support.annotation.StringRes;
19 | import android.support.graphics.drawable.VectorDrawableCompat;
20 | import android.text.TextUtils;
21 | import android.util.AttributeSet;
22 | import android.util.Log;
23 | import android.view.GestureDetector;
24 | import android.view.MotionEvent;
25 | import android.view.ScaleGestureDetector;
26 |
27 | import com.junmeng.gdv.detector.MoveGestureDetector;
28 | import com.junmeng.rdetecte.R;
29 | import com.junmeng.rdetecte.bean.MapPathInfo;
30 | import com.junmeng.rdetecte.bean.VectorPathInfo;
31 | import com.junmeng.rdetecte.utils.CommonUtil;
32 | import com.junmeng.rdetecte.utils.VectorMapParser;
33 |
34 | import java.util.HashMap;
35 | import java.util.Map;
36 |
37 |
38 | /**
39 | * Created by HWJ on 2017/2/17.
40 | */
41 |
42 | public class RegionDetectSurfaceView extends BaseSurfaceView {
43 | private static final String TAG = "RegionDetectSurface";
44 |
45 | public interface OnRegionDetectListener {
46 | /**
47 | * 所有区域检测
48 | *
49 | * @param name
50 | */
51 | void onRegionDetect(String name);
52 | }
53 |
54 | public interface OnActivateRegionDetectListener {
55 | /**
56 | * 激活区域检测
57 | *
58 | * @param name
59 | */
60 | void onActivateRegionDetect(String name);
61 | }
62 |
63 | public interface OnDoubleClickListener {
64 | /**
65 | * 双击事件
66 | *
67 | * @param scaleMode
68 | */
69 | void onDoubleClick(@ScaleMode int scaleMode);
70 | }
71 |
72 | /**
73 | * 中心定位检测模式
74 | */
75 | public static final int REGION_DETECT_MODE_CENTER = 0;
76 | /**
77 | * 手动点击检测模式
78 | */
79 | public static final int REGION_DETECT_MODE_CLICK = 1;
80 |
81 | @IntDef({REGION_DETECT_MODE_CENTER, REGION_DETECT_MODE_CLICK})
82 | public @interface RegionDetectMode {
83 | }
84 |
85 | /**
86 | * 放大
87 | */
88 | public static final int SCALE_ZOOMIN = 0;
89 | /**
90 | * 缩小
91 | */
92 | public static final int SCALE_ZOOMOUT = 1;
93 |
94 | @IntDef({SCALE_ZOOMIN, SCALE_ZOOMOUT})
95 | public @interface ScaleMode {
96 | }
97 |
98 | /**
99 | * 中心点图标以图标中心为中心点
100 | */
101 | public static final int CENTER_ICON_POSITION_CENTER = 0;
102 | /**
103 | * 中心点图标以底部为中心点
104 | */
105 | public static final int CENTER_ICON_POSITION_BOTTOM = 1;
106 |
107 | @IntDef({CENTER_ICON_POSITION_CENTER, CENTER_ICON_POSITION_BOTTOM})
108 | public @interface CenterIconLocationType {
109 | }
110 |
111 |
112 | //模式设置
113 | @CenterIconLocationType
114 | private int centerIconLocationType = CENTER_ICON_POSITION_BOTTOM;
115 |
116 | @RegionDetectMode
117 | private int regionDetectMode = REGION_DETECT_MODE_CENTER;
118 |
119 | @Deprecated
120 | private VectorDrawableCompat vectorDrawableCompat;//测试时绘制原始地图
121 |
122 | private VectorPathInfo vectorPathInfo;//从xml中解析出的vector信息
123 | private VectorMapParser vectorMapParser;
124 |
125 | private Paint paint = new Paint();
126 | private OnRegionDetectListener onRegionListener;
127 | private OnActivateRegionDetectListener onActivateRegionListener;
128 | private OnDoubleClickListener onDoubleClickListener;
129 |
130 | private float scale = 1f;//实际缩放比例,相对于起始图片的缩放比例
131 |
132 | private float originalScale = 1f;//最原始的缩放比例
133 | private float maxScale = 3.0f;//最大缩放比例
134 | private float minScale = originalScale;//最小缩放比例,最小默认为最合适的居中的比例,这里就不提供设置了
135 |
136 | //平移的差值,相对于起始位置的差值
137 | private float translateDx = 0;
138 | private float translateDy = 0;
139 |
140 | private float originalTranslateDx = 0;//最原始的平移差值
141 | private float originalTranslateDy = 0;//最原始的平移差值
142 |
143 | //屏幕宽高
144 | private int screenWidth = 0;
145 | private int screenHeight = 0;
146 |
147 | //屏幕中心点的坐标
148 | private float screenCenterX = 0;
149 | private float screenCenterY = 0;
150 |
151 | //地图原始宽高,与xml中vector的viewportWidth和viewportHeight一致
152 | private int mapOriginalWidth = 700;
153 | private int mapOriginalHeight = 600;
154 |
155 | //地图起始位置的中心坐标
156 | private float mapOriginalCenterX = mapOriginalWidth / 2f;
157 | private float mapOriginalCenterY = mapOriginalHeight / 2f;
158 |
159 | //中心定位点的图标
160 | private Bitmap centerIcon;
161 |
162 | private MoveGestureDetector moveGestureDetector;
163 | private ScaleGestureDetector scaleGestureDetector;
164 | private GestureDetector gestureDetector;
165 |
166 |
167 | //地图的矩阵,用于缩放
168 | private Matrix mapPathMatrix = new Matrix();
169 |
170 | //存放地图绘制path
171 | private HashMap pathInfoMap = new HashMap<>();
172 |
173 | //当前中心定位点指向的路径path的key值
174 | private String currentKey = "";
175 |
176 | //上一个中心定位点指向的路径path的key值
177 | private String lastKey = "";
178 |
179 | //是否是debug模式,自己用
180 | private boolean isDebugMode = false;
181 |
182 | //区域默认的颜色
183 | private int highlightColor = 0x80BB945A;
184 | private int activateAreaColor = 0x802F8BBB;
185 | private int normalAreaColor = 0x8069BBA8;
186 |
187 | //中心定位点图标是否可见
188 | private boolean isCenterIconVisible = true;
189 |
190 | //是否支持双击缩放操作
191 | private boolean isSupportDoubleScale = true;
192 |
193 | private int animateTime = 300;//动画时间,ms
194 |
195 | private boolean isOpenCenterLocation = true;//是否启用中心定位点
196 |
197 | private String selectedActivateKey = "";//选中的激活区域(会高亮显示),只有在isOpenCenterLocation为false才生效
198 |
199 | public RegionDetectSurfaceView(Context context) {
200 | this(context, null);
201 | }
202 |
203 | public RegionDetectSurfaceView(Context context, AttributeSet attrs) {
204 | this(context, attrs, 0);
205 | }
206 |
207 | public RegionDetectSurfaceView(Context context, AttributeSet attrs, int defStyleAttr) {
208 | super(context, attrs, defStyleAttr);
209 | init(context);
210 | }
211 |
212 | private void init(Context context) {
213 | moveGestureDetector = new MoveGestureDetector(context, new MyMoveGestureListener());
214 | scaleGestureDetector = new ScaleGestureDetector(context, new MyScaleGestureListener());
215 | gestureDetector = new GestureDetector(context, new MyGestureListener());
216 | vectorMapParser = new VectorMapParser();
217 | setAreaMap(R.drawable.ic_map_china);
218 |
219 | centerIcon = CommonUtil.getBitmapFromVectorDrawable(context, R.drawable.ic_location_24dp);
220 |
221 | initPaint();
222 |
223 | }
224 |
225 | //******************************************开放接口*****************************************
226 |
227 | /**
228 | * 设置区域检测模式(支持中心定位检测和手动点击检测两种)
229 | * 默认为中心定位检测
230 | *
231 | * @param detectMode
232 | */
233 | public void setRegionDetectMode(@RegionDetectMode int detectMode) {
234 | this.regionDetectMode = detectMode;
235 | }
236 |
237 | /**
238 | * 设置中心图标的定位位置
239 | *
240 | * @param locationType 图标的定位中心位置
241 | */
242 | public void setCenterIconLocationType(@CenterIconLocationType int locationType) {
243 | this.centerIconLocationType = locationType;
244 | // invalidate();
245 | }
246 |
247 | /**
248 | * 设置中心图标
249 | *
250 | * @param bitmap
251 | */
252 | public void setCenterIcon(Bitmap bitmap) {
253 | if (bitmap != null) {
254 | this.centerIcon = bitmap;
255 | // invalidate();
256 | }
257 | }
258 |
259 | /**
260 | * 将地图合适居中
261 | */
262 | public void fitCenter() {
263 | animateToFitCenter();
264 | }
265 |
266 | /**
267 | * 设置区域颜色
268 | *
269 | * @param highlightColor 高亮颜色,-1表示不设置
270 | * @param activatedColor 激活颜色,-1表示不设置
271 | * @param normalColor 正常颜色,-1表示不设置
272 | */
273 | public void setAreaColor(@StringRes int areaNameRes, @ColorInt int highlightColor, @ColorInt int activatedColor, @ColorInt int normalColor) {
274 | String key = getResources().getString(areaNameRes);
275 | MapPathInfo info = pathInfoMap.get(key);
276 | if (info != null) {
277 | info.highlightColor = highlightColor;
278 | info.activatedColor = activatedColor;
279 | info.normalColor = normalColor;
280 | }
281 | //invalidate();
282 | }
283 |
284 | /**
285 | * 设置区域颜色
286 | *
287 | * @param highlightColor 高亮颜色,-1表示不设置
288 | * @param activatedColor 激活颜色,-1表示不设置
289 | * @param normalColor 正常颜色,-1表示不设置
290 | */
291 | public void setAreaColor(@NonNull String areaName, @ColorInt int highlightColor, @ColorInt int activatedColor, @ColorInt int normalColor) {
292 | MapPathInfo info = pathInfoMap.get(areaName);
293 | if (info != null) {
294 | info.highlightColor = highlightColor;
295 | info.activatedColor = activatedColor;
296 | info.normalColor = normalColor;
297 | }
298 | // invalidate();
299 | }
300 |
301 | /**
302 | * 设置默认的高亮颜色,优先级最低
303 | *
304 | * @param color
305 | */
306 | public void setDefaultHighlightColor(@ColorInt int color) {
307 | this.highlightColor = color;
308 | //invalidate();
309 | }
310 |
311 | /**
312 | * 设置默认的激活颜色,优先级最低
313 | *
314 | * @param color
315 | */
316 | public void setDefaultActivateColor(@ColorInt int color) {
317 | this.activateAreaColor = color;
318 | //invalidate();
319 | }
320 |
321 | /**
322 | * 设置默认的正常颜色,优先级最低
323 | *
324 | * @param color
325 | */
326 | public void setDefaultNormalColor(@ColorInt int color) {
327 | this.normalAreaColor = color;
328 | // invalidate();
329 | }
330 |
331 | /**
332 | * 设置区域激活状态
333 | *
334 | * @param areaNameRes
335 | * @param isActivated
336 | */
337 | public void setAreaActivateStatus(@StringRes int areaNameRes, boolean isActivated) {
338 | String key = getResources().getString(areaNameRes);
339 | if (pathInfoMap.get(key) != null) {
340 |
341 | pathInfoMap.get(key).isActivated = isActivated;
342 | }
343 | // invalidate();
344 | }
345 |
346 | /**
347 | * 设置区域激活状态
348 | *
349 | * @param areaNameRes
350 | * @param isActivated 是否激活
351 | */
352 | public void setAreaActivateStatus(@NonNull @StringRes int[] areaNameRes, boolean isActivated) {
353 | for (int res : areaNameRes) {
354 | String key = getResources().getString(res);
355 | if (pathInfoMap.get(key) != null) {
356 | pathInfoMap.get(key).isActivated = isActivated;
357 | }
358 | }
359 | // invalidate();
360 |
361 | }
362 |
363 |
364 | /**
365 | * 设置区域激活状态
366 | *
367 | * @param areaNames
368 | */
369 | public void setAreaActivateStatus(@NonNull String[] areaNames, boolean isActivated) {
370 | for (String key : areaNames) {
371 | if (pathInfoMap.get(key) != null) {
372 | pathInfoMap.get(key).isActivated = isActivated;
373 | }
374 | }
375 | // invalidate();
376 | }
377 |
378 | /**
379 | * 设置区域激活状态
380 | *
381 | * @param areaName
382 | */
383 | public void setAreaActivateStatus(String areaName, boolean isActivated) {
384 | if (pathInfoMap.get(areaName) != null) {
385 | pathInfoMap.get(areaName).isActivated = isActivated;
386 | }
387 | // invalidate();
388 | }
389 |
390 | /**
391 | * 设置是否激活所有区域
392 | *
393 | * @param isActivated
394 | */
395 | public void setAllAreaActivateStatus(boolean isActivated) {
396 | for (String key : pathInfoMap.keySet()) {
397 | pathInfoMap.get(key).isActivated = isActivated;
398 | }
399 | //invalidate();
400 | }
401 |
402 | /**
403 | * 设置激活地图区域监听器,只有激活区域能触发
404 | *
405 | * @param listener
406 | */
407 | public void setOnActivateRegionDetectListener(OnActivateRegionDetectListener listener) {
408 | this.onActivateRegionListener = listener;
409 | }
410 |
411 | /**
412 | * 设置地图区域监听器
413 | *
414 | * @param listener
415 | */
416 | public void setOnRegionDetectListener(OnRegionDetectListener listener) {
417 | this.onRegionListener = listener;
418 | }
419 |
420 | /**
421 | * 设置双击事件监听器
422 | *
423 | * @param listener
424 | */
425 | public void setOnDoubleClickListener(OnDoubleClickListener listener) {
426 | this.onDoubleClickListener = listener;
427 | }
428 |
429 | /**
430 | * 设置区域地图
431 | *
432 | * @param map vector图
433 | */
434 | public void setAreaMap(@DrawableRes int map) {
435 |
436 | vectorPathInfo = vectorMapParser.parse(getResources(), map);
437 | this.mapOriginalWidth = (int) vectorPathInfo.getViewportWidth();
438 | this.mapOriginalHeight = (int) vectorPathInfo.getViewportHeight();
439 | vectorDrawableCompat = VectorDrawableCompat.create(getResources(), map, null);
440 | if (vectorDrawableCompat != null) {
441 | vectorDrawableCompat.setBounds(new Rect(0, 0, mapOriginalWidth, mapOriginalHeight));
442 | }
443 | initAreaPathMap();
444 | initScaleAndTranslate();
445 | //invalidate();
446 | }
447 |
448 | /**
449 | * 获得当前所在区域名称
450 | *
451 | * @return
452 | */
453 | public String getCurrentAreaName() {
454 | return currentKey;
455 | }
456 |
457 | /**
458 | * 判断某个区域是否是激活区域
459 | *
460 | * @param areaName
461 | * @return
462 | */
463 | public boolean isActivatedArea(String areaName) {
464 | if (pathInfoMap.get(areaName) != null) {
465 | return pathInfoMap.get(areaName).isActivated;
466 | }
467 | return false;
468 | }
469 |
470 | /**
471 | * 设置选中的激活区域(在关闭中心定位点的情况下才生效,表现为高亮显示)
472 | *
473 | * @param areaName
474 | */
475 | public void setSelectedAreaOnlyCloseCenterLocation(String areaName) {
476 | if (TextUtils.isEmpty(areaName)) {
477 | return;
478 | }
479 | selectedActivateKey = areaName;
480 | }
481 |
482 | /**
483 | * 设置选中的激活区域(在关闭中心定位点的情况下才生效,表现为高亮显示)
484 | *
485 | * @param areaNameRes
486 | */
487 | public void setSelectedAreaOnlyCloseCenterLocation(@StringRes int areaNameRes) {
488 | String areaName = getResources().getString(areaNameRes);
489 | if (TextUtils.isEmpty(areaName)) {
490 | return;
491 | }
492 | selectedActivateKey = areaName;
493 | }
494 |
495 | /**
496 | * 是否启用中心定位点,关闭则如普通地图一样只能缩放位移
497 | * 在中心定位检测模式下设置有效
498 | *
499 | * @param isOpen
500 | */
501 | public void isOpenCenterLocation(boolean isOpen) {
502 | if (regionDetectMode == REGION_DETECT_MODE_CENTER) {
503 | isOpenCenterLocation = isOpen;
504 | isCenterIconVisible = isOpen;
505 | }
506 |
507 | }
508 |
509 | /**
510 | * 检测坐标所在的区域
511 | *
512 | * @param x
513 | * @param y
514 | * @return 检测到的区域名称
515 | */
516 | public String detectRegionByCoordinate(float x, float y) {
517 | PointF pf = getCoordinateOpppsiteToOriginalMap(x, y);
518 | String areaName = "";
519 | //区域检测
520 | for (String key : pathInfoMap.keySet()) {
521 | Region region = pathInfoMap.get(key).region;
522 | if (region.contains((int) pf.x, (int) pf.y)) {
523 | areaName = key;
524 | break;
525 | }
526 | }
527 | return areaName;
528 | }
529 |
530 | /**
531 | * 设置缩放动画时间,默认300毫秒
532 | *
533 | * @param ms 毫秒
534 | */
535 | public void setAnimateTime(int ms) {
536 | this.animateTime = ms;
537 | }
538 |
539 | /**
540 | * 是否支持双击缩放操作,默认支持
541 | *
542 | * @param isSupport
543 | */
544 | public void isSupportDoubleClickScale(boolean isSupport) {
545 | isSupportDoubleScale = isSupport;
546 | }
547 |
548 | /**
549 | * 设置最大缩放比例(默认为最小的3倍),如果小于默认的最小比例则设置无效
550 | *
551 | * @param scale
552 | */
553 | public void setMaxScale(float scale) {
554 | if (scale > originalScale) {
555 | this.maxScale = scale;
556 | }
557 | }
558 |
559 | /**
560 | * 获得最大的缩放比例
561 | *
562 | * @return
563 | */
564 | public float getMaxScale() {
565 | return maxScale;
566 | }
567 |
568 |
569 | /**
570 | * 获得当前的缩放比例
571 | *
572 | * @return
573 | */
574 | public float getCurrentScale() {
575 | return scale;
576 | }
577 |
578 | /**
579 | * 获得最小的缩放比例
580 | *
581 | * @return
582 | */
583 | public float getMinScale() {
584 | return originalScale;
585 | }
586 |
587 | /**
588 | * 缩放地图
589 | *
590 | * @param scale
591 | */
592 | public void scaleMap(float scale) {
593 | mapPathMatrix.postScale(scale, scale, mapOriginalCenterX, mapOriginalCenterY);
594 | this.scale *= scale;
595 | Log.i(TAG, "onScale: " + scale);
596 |
597 | }
598 |
599 | /**
600 | * 平移地图
601 | *
602 | * @param translateDx
603 | * @param translateDy
604 | */
605 | public void translateMap(float translateDx, float translateDy) {
606 | this.translateDx = translateDx;
607 | this.translateDy = translateDy;
608 | }
609 |
610 |
611 | /**
612 | * 设置中心图标是否可见
613 | *
614 | * @param isVisible
615 | */
616 | public void setCenterIconVisibility(boolean isVisible) {
617 | isCenterIconVisible = isVisible;
618 | // invalidate();
619 | }
620 |
621 | /**
622 | * 获得所有VectorDrawableCompat中的所有path,包含第一级组里的path(暂不支持第二级及更深层次的)
623 | *
624 | * @return
625 | */
626 | public Map getAllPath() {
627 | return CommonUtil.getPaths(vectorPathInfo.getPaths());
628 | }
629 |
630 |
631 | @Override
632 | public boolean onTouchEvent(MotionEvent event) {
633 | moveGestureDetector.onTouchEvent(event);
634 | scaleGestureDetector.onTouchEvent(event);
635 | gestureDetector.onTouchEvent(event);
636 | return true;
637 | }
638 |
639 |
640 | @Override
641 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
642 | super.onMeasure(widthMeasureSpec, heightMeasureSpec);
643 | screenWidth = getMeasuredWidth();
644 | screenHeight = getMeasuredHeight();
645 | initScaleAndTranslate();
646 | }
647 |
648 | /**
649 | * 在此处执行绘制过程
650 | *
651 | * @param canvas
652 | */
653 | @Override
654 | public void doDraw(Canvas canvas) {
655 | //long pre=System.currentTimeMillis();
656 | canvas.save();
657 | canvas.translate(translateDx, translateDy);
658 |
659 | //绘制地图各省市
660 | paint.setColor(Color.GRAY);
661 | Map copyMap = new HashMap<>();//复制一份,防止引发ConcurrentModificationException
662 | copyMap.putAll(pathInfoMap);
663 | for (String key : copyMap.keySet()) {
664 | MapPathInfo mapPathInfo = copyMap.get(key);
665 | Path path = new Path();
666 | path.addPath(mapPathInfo.path, mapPathMatrix);
667 |
668 | if (currentKey.equals(key)) {//被选中的
669 |
670 | if (mapPathInfo.isActivated) {//是否激活了的
671 | paint.setColor(isOpenCenterLocation ?
672 | (mapPathInfo.highlightColor != -1 ? mapPathInfo.highlightColor : highlightColor)
673 | : (mapPathInfo.activatedColor != -1 ? mapPathInfo.activatedColor : activateAreaColor));
674 | } else {
675 | paint.setColor(mapPathInfo.normalColor != -1 ? mapPathInfo.normalColor : normalAreaColor);
676 | }
677 |
678 | } else {//未被选中的
679 | if (mapPathInfo.isActivated) {
680 | paint.setColor(mapPathInfo.activatedColor != -1 ? mapPathInfo.activatedColor : activateAreaColor);
681 | } else {
682 | paint.setColor(mapPathInfo.normalColor != -1 ? mapPathInfo.normalColor : normalAreaColor);
683 | }
684 |
685 | }
686 | //只有在关闭中心定位点并选中了激活区域才会高亮显示
687 | if (!isOpenCenterLocation && mapPathInfo.isActivated && selectedActivateKey.equals(key)) {
688 | paint.setColor((mapPathInfo.highlightColor != -1 ? mapPathInfo.highlightColor : highlightColor));
689 | }
690 | canvas.drawPath(path, paint);
691 |
692 | }
693 |
694 | canvas.restore();
695 |
696 | //绘制中心定位点
697 | if (centerIcon != null && isCenterIconVisible && regionDetectMode == REGION_DETECT_MODE_CENTER) {
698 | canvas.drawBitmap(centerIcon, (screenCenterX - centerIcon.getWidth() / 2f),
699 | centerIconLocationType == CENTER_ICON_POSITION_BOTTOM ?
700 | (screenCenterY - centerIcon.getHeight()) :
701 | (screenCenterY - centerIcon.getHeight() / 2f), null);
702 | }
703 |
704 | if (isDebugMode) {
705 | drawDebugView(canvas);
706 | }
707 | //Log.i(TAG, "onDraw: "+ (System.currentTimeMillis()-pre));
708 | }
709 |
710 | /**
711 | * 初始化区域路径Map
712 | */
713 | private void initAreaPathMap() {
714 | pathInfoMap.clear();
715 | HashMap pathsMap = (HashMap) getAllPath();
716 | for (String key : pathsMap.keySet()) {
717 | MapPathInfo mapPathInfo = new MapPathInfo(pathsMap.get(key));
718 | pathInfoMap.put(key, mapPathInfo);
719 | }
720 | }
721 |
722 | private void initPaint() {
723 | paint.setColor(Color.RED);
724 | paint.setStyle(Paint.Style.FILL);
725 | paint.setStrokeWidth(2);
726 | }
727 |
728 | /**
729 | * 初始化原始的缩放和位移等相关变量
730 | */
731 | private void initScaleAndTranslate() {
732 | screenCenterX = screenWidth / 2f;
733 | screenCenterY = screenHeight / 2f;
734 | mapOriginalCenterX = mapOriginalWidth / 2f;
735 | mapOriginalCenterY = mapOriginalHeight / 2f;
736 |
737 | float scaleX = screenWidth * 1f / mapOriginalWidth;
738 | float scaleY = screenHeight * 1f / mapOriginalHeight;
739 | minScale = originalScale = scale = Math.min(scaleX, scaleY);// 获得缩放比例最大的那个缩放比,即scaleX和scaleY中小的那个
740 | maxScale = originalScale * 3;
741 | Log.i(TAG, "onMeasure: " + screenWidth + "*" + screenHeight + "," + scale);
742 | originalTranslateDx = translateDx = (screenWidth - mapOriginalWidth) / 2f;
743 | originalTranslateDy = translateDy = (screenHeight - mapOriginalHeight) / 2f;
744 | mapPathMatrix.setScale(originalScale, originalScale, mapOriginalCenterX, mapOriginalCenterY);
745 | }
746 |
747 | /**
748 | * 区域检测
749 | */
750 | private void areaDetect() {
751 | if (!isOpenCenterLocation || regionDetectMode != REGION_DETECT_MODE_CENTER) {
752 | return;
753 | }
754 | currentKey = "";
755 | boolean isActivate = false;
756 | //区域检测
757 | for (String key : pathInfoMap.keySet()) {
758 | Region region = pathInfoMap.get(key).region;
759 | if (onRegionListener == null && pathInfoMap.get(key).isActivated == false) {
760 | continue;
761 | }
762 | if (region.contains((int) getMapCenterOppositeCoordinate().x, (int) getMapCenterOppositeCoordinate().y)) {
763 | Log.i(TAG, "选中了 " + key);
764 | currentKey = key;
765 | isActivate = pathInfoMap.get(key).isActivated;
766 | break;
767 | }
768 | }
769 | if (onRegionListener != null) {
770 | if (!lastKey.equals(currentKey)) {
771 | onRegionListener.onRegionDetect(currentKey);
772 | }
773 |
774 | }
775 | if (onActivateRegionListener != null) {
776 | if (!lastKey.equals(currentKey)) {
777 | onActivateRegionListener.onActivateRegionDetect(isActivate ? currentKey : "");
778 | }
779 |
780 | }
781 | lastKey = currentKey;
782 | }
783 |
784 |
785 | /**
786 | * 获得中心定位点在地图起始位置的相对坐标
787 | * 主要用于区域检测
788 | *
789 | * @return
790 | */
791 | private PointF getMapCenterOppositeCoordinate() {
792 |
793 | PointF mapCenter = getMapCenterCoordinate();
794 | //中心定位点与实际地图中点的差值
795 | float dx = screenCenterX - mapCenter.x;
796 | float dy = screenCenterY - mapCenter.y;
797 |
798 | //根据位移的差值和缩放比例计算中心定位点在地图起始位置的相对坐标
799 | //主要用于区域检测
800 | float x = mapOriginalCenterX + dx / scale;
801 | float y = mapOriginalCenterY + dy / scale;
802 | return new PointF(x, y);
803 | }
804 |
805 | /**
806 | * 获得指定点在起始地图的相对坐标
807 | * 主要用于区域检测
808 | *
809 | * @param x 绝对坐标x
810 | * @param y 绝对坐标y
811 | * @return
812 | */
813 | private PointF getCoordinateOpppsiteToOriginalMap(float x, float y) {
814 |
815 | PointF mapCenter = getMapCenterCoordinate();
816 | //中心定位点与实际地图中点的差值
817 | float dx = x - mapCenter.x;
818 | float dy = y - mapCenter.y;
819 |
820 | //根据位移的差值和缩放比例计算中心定位点在地图起始位置的相对坐标
821 | //主要用于区域检测
822 | float ox = mapOriginalCenterX + dx / scale;
823 | float oy = mapOriginalCenterY + dy / scale;
824 | return new PointF(ox, oy);
825 | }
826 |
827 |
828 | /**
829 | * 获得实际地图(即经过平移和缩放后的地图)的中心坐标
830 | *
831 | * @return
832 | */
833 | private PointF getMapCenterCoordinate() {
834 |
835 | float mapCenterX = mapOriginalWidth / 2f + translateDx;
836 | float mapCenterY = mapOriginalHeight / 2f + translateDy;
837 |
838 | return new PointF(mapCenterX, mapCenterY);
839 | }
840 |
841 |
842 | /**
843 | * 绘制测试画面,调试用
844 | *
845 | * @param canvas
846 | */
847 | private void drawDebugView(Canvas canvas) {
848 |
849 | //绘制原始地图
850 | if (vectorDrawableCompat != null) {
851 | vectorDrawableCompat.draw(canvas);
852 | }
853 |
854 | paint.setColor(Color.BLACK);
855 | paint.setTextSize(30);
856 | canvas.drawText("缩放:" + scale, 20, 50, paint);
857 | canvas.drawText("选中:" + currentKey, 300, 50, paint);
858 |
859 |
860 | //实际地图(即经过平移和缩放后的地图)的中心坐标
861 | float mapCenterX = mapOriginalWidth / 2f + translateDx;
862 | float mapCenterY = mapOriginalHeight / 2f + translateDy;
863 |
864 |
865 | //中心定位点与实际地图中点的差值
866 | float dx = screenCenterX - mapCenterX;
867 | float dy = screenCenterY - mapCenterY;
868 |
869 | //根据位移的差值和缩放比例计算中心定位点在地图起始位置的相对坐标
870 | //主要用于区域检测
871 | float x = mapOriginalCenterX + dx / scale;
872 | float y = mapOriginalCenterY + dy / scale;
873 |
874 | //绘制实际地图的中心点
875 | paint.setColor(Color.BLUE);
876 | canvas.drawCircle(mapCenterX, mapCenterY, 8, paint);
877 | canvas.drawText("中心:(" + mapCenterY + "," + mapCenterY + ")", mapCenterX, mapCenterY, paint);
878 |
879 | //起始地图的中心点
880 | canvas.drawCircle(mapOriginalCenterX, mapOriginalCenterY, 8, paint);
881 | canvas.drawText("中心:(" + mapOriginalCenterX + "," + mapOriginalCenterY + ")", mapOriginalCenterX, mapOriginalCenterY, paint);
882 |
883 | //绘制中心定位点在原图起始位置的相对坐标
884 | paint.setColor(Color.RED);
885 | canvas.drawCircle(x, y, 8, paint);
886 | canvas.drawText("(" + x + "," + y + ")", x, y, paint);
887 |
888 | canvas.drawText("(" + screenCenterX + "," + screenCenterY + ")", screenCenterX, screenCenterY, paint);
889 | }
890 |
891 |
892 | private class MyMoveGestureListener extends MoveGestureDetector.SimpleOnMoveGestureListener {
893 |
894 | @Override
895 | public boolean onMove(MoveGestureDetector detector) {
896 | PointF d = detector.getFocusDelta();
897 | if (Math.abs(d.x) < 2 && Math.abs(d.y) < 2) {//忽略小范围的滑动
898 | return true;
899 | }
900 | translateDx += d.x;
901 | translateDy += d.y;
902 | Log.i(TAG, "onMove: " + d.x + "," + d.y);
903 | areaDetect();
904 | return true;
905 | }
906 |
907 | @Override
908 | public boolean onMoveBegin(MoveGestureDetector detector) {
909 | return super.onMoveBegin(detector);
910 | }
911 |
912 | @Override
913 | public void onMoveEnd(MoveGestureDetector detector) {
914 | super.onMoveEnd(detector);
915 | Log.i(TAG, "onMoveEnd:");
916 |
917 | }
918 | }
919 |
920 | private class MyScaleGestureListener extends ScaleGestureDetector.SimpleOnScaleGestureListener {
921 |
922 | @Override
923 | public boolean onScale(ScaleGestureDetector detector) {
924 | float factor = detector.getScaleFactor();
925 | if (Math.abs(factor - 1) < 0.015) {
926 | return true;
927 | }
928 | scaleMap(factor);
929 | areaDetect();
930 | return true;
931 | }
932 |
933 | @Override
934 | public boolean onScaleBegin(ScaleGestureDetector detector) {
935 | return super.onScaleBegin(detector);
936 | }
937 |
938 | @Override
939 | public void onScaleEnd(ScaleGestureDetector detector) {
940 | super.onScaleEnd(detector);
941 | if (scale > maxScale) {
942 | animateToTargetScale(maxScale);
943 | }
944 | if (scale < originalScale) {
945 | animateToTargetScale(originalScale);
946 | }
947 |
948 |
949 | }
950 | }
951 |
952 | private class MyGestureListener extends GestureDetector.SimpleOnGestureListener {
953 | @Override
954 | public boolean onDoubleTap(MotionEvent e) {
955 | Log.i(TAG, "onDoubleTap: ");
956 | //双击
957 | if (!isSupportDoubleScale) {
958 | return super.onDoubleTap(e);
959 | }
960 | if (scale >= maxScale) {//缩小为原来的大小
961 | if (onDoubleClickListener != null) {
962 | onDoubleClickListener.onDoubleClick(SCALE_ZOOMOUT);
963 | }
964 | animateToFitCenter();
965 | } else {//缩放到最大值
966 | if (onDoubleClickListener != null) {
967 | onDoubleClickListener.onDoubleClick(SCALE_ZOOMIN);
968 | }
969 | animateToTargetScale(maxScale);
970 | }
971 | return super.onDoubleTap(e);
972 | }
973 |
974 | @Override
975 | public boolean onSingleTapConfirmed(MotionEvent e) {
976 | Log.i(TAG, "onSingleTapConfirmed: " + e.getX() + "," + e.getY());
977 | //单击
978 | if (regionDetectMode == REGION_DETECT_MODE_CLICK) {
979 | String name = detectRegionByCoordinate(e.getX(), e.getY());
980 | Log.i(TAG, "key=" + name);
981 | if (onRegionListener != null) {
982 | onRegionListener.onRegionDetect(name);
983 | }
984 | MapPathInfo mapPathInfo = pathInfoMap.get(name);
985 | if (mapPathInfo != null && mapPathInfo.isActivated && onActivateRegionListener != null) {
986 | currentKey = name;
987 | onActivateRegionListener.onActivateRegionDetect(name);
988 | }
989 | }
990 | return super.onSingleTapConfirmed(e);
991 | }
992 | }
993 |
994 | /**
995 | * 缩放到目标比例targetScale
996 | *
997 | * @param targetScale
998 | */
999 | private void animateToTargetScale(final float targetScale) {
1000 | ValueAnimator valueAnimator = ValueAnimator.ofFloat(scale, targetScale);
1001 | valueAnimator.setDuration(animateTime);
1002 | valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
1003 | @Override
1004 | public void onAnimationUpdate(ValueAnimator animation) {
1005 | float animatorValue = (float) animation.getAnimatedValue();
1006 | scaleMap(animatorValue / scale);
1007 | if (animatorValue == targetScale) {
1008 | areaDetect();
1009 | }
1010 | }
1011 | });
1012 | valueAnimator.start();
1013 | }
1014 |
1015 | /**
1016 | * 缩放到合适居中的比例
1017 | */
1018 | private void animateToFitCenter() {
1019 | ValueAnimator valueAnimator = ValueAnimator.ofFloat(scale, originalScale);
1020 | valueAnimator.setDuration(animateTime);
1021 | valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
1022 | @Override
1023 | public void onAnimationUpdate(ValueAnimator animation) {
1024 | float animatorValue = (float) animation.getAnimatedValue();
1025 |
1026 | scaleMap(animatorValue / scale);
1027 | }
1028 | });
1029 |
1030 | ValueAnimator valueAnimator2 = ValueAnimator.ofFloat(translateDx, originalTranslateDx);
1031 | valueAnimator2.setDuration(animateTime);
1032 | valueAnimator2.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
1033 | @Override
1034 | public void onAnimationUpdate(ValueAnimator animation) {
1035 | float animatorValue = (float) animation.getAnimatedValue();
1036 |
1037 |
1038 | translateMap(animatorValue, translateDy);
1039 |
1040 | }
1041 | });
1042 |
1043 | ValueAnimator valueAnimator3 = ValueAnimator.ofFloat(translateDy, originalTranslateDy);
1044 | valueAnimator3.setDuration(animateTime);
1045 | valueAnimator3.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
1046 | @Override
1047 | public void onAnimationUpdate(ValueAnimator animation) {
1048 | float animatorValue = (float) animation.getAnimatedValue();
1049 |
1050 | translateMap(translateDx, animatorValue);
1051 | if (animatorValue == originalTranslateDy) {
1052 | areaDetect();
1053 | }
1054 | }
1055 | });
1056 | valueAnimator.start();
1057 | valueAnimator2.start();
1058 | valueAnimator3.start();
1059 | }
1060 |
1061 |
1062 | }
1063 |
--------------------------------------------------------------------------------
/lib_regiondetector/src/main/res/drawable/ic_location_24dp.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/lib_regiondetector/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | RegionDetector
3 | 北京
4 | 天津
5 | 上海
6 | 重庆
7 | 河北
8 | 山西
9 | 辽宁
10 | 吉林
11 | 黑龙江
12 | 江苏
13 | 浙江
14 | 安徽
15 | 福建
16 | 江西
17 | 山东
18 | 河南
19 | 湖北
20 | 湖南
21 | 广东
22 | 海南
23 | 四川
24 | 贵州
25 | 云南
26 | 陕西
27 | 甘肃
28 | 青海
29 | 台湾
30 | 内蒙古
31 | 广西
32 | 西藏
33 | 宁夏
34 | 新疆
35 | 香港
36 |
37 |
--------------------------------------------------------------------------------
/lib_regiondetector/src/test/java/com/junmeng/rdetecte/ExampleUnitTest.java:
--------------------------------------------------------------------------------
1 | package com.junmeng.rdetecte;
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 | }
--------------------------------------------------------------------------------
/screenshots/QQ截图20170220173408.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/android-coding-well/RegionDetector/534469597d194ec162462ff8ef5773239247cb4d/screenshots/QQ截图20170220173408.png
--------------------------------------------------------------------------------
/screenshots/device-2017-02-20-174131.mp4_1487584213.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/android-coding-well/RegionDetector/534469597d194ec162462ff8ef5773239247cb4d/screenshots/device-2017-02-20-174131.mp4_1487584213.gif
--------------------------------------------------------------------------------
/screenshots/device-2017-02-20-174131.mp4_1487584269.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/android-coding-well/RegionDetector/534469597d194ec162462ff8ef5773239247cb4d/screenshots/device-2017-02-20-174131.mp4_1487584269.gif
--------------------------------------------------------------------------------
/screenshots/device-2017-02-20-174131.mp4_1487584566.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/android-coding-well/RegionDetector/534469597d194ec162462ff8ef5773239247cb4d/screenshots/device-2017-02-20-174131.mp4_1487584566.gif
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app', ':lib_regiondetector'
2 |
--------------------------------------------------------------------------------