├── .gitignore
├── README.md
├── app
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── com
│ │ └── lzy
│ │ └── hexagonviewdemo
│ │ └── ApplicationTest.java
│ ├── main
│ ├── AndroidManifest.xml
│ ├── java
│ │ └── com
│ │ │ └── lzy
│ │ │ └── hexagonviewdemo
│ │ │ └── HexagonActivity.java
│ └── res
│ │ ├── layout
│ │ └── activity_hexagon.xml
│ │ ├── mipmap-hdpi
│ │ ├── hexagon_image.jpg
│ │ └── ic_launcher.png
│ │ └── values
│ │ ├── colors.xml
│ │ ├── dimens.xml
│ │ ├── strings.xml
│ │ └── styles.xml
│ └── test
│ └── java
│ └── com
│ └── lzy
│ └── hexagonviewdemo
│ └── ExampleUnitTest.java
├── build.gradle
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── hexagonview
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ └── main
│ ├── AndroidManifest.xml
│ ├── java
│ └── com
│ │ └── lzy
│ │ └── widget
│ │ └── HexagonView.java
│ └── res
│ └── values
│ └── attrs.xml
└── settings.gradle
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | .idea
4 | /local.properties
5 | /screenshots
6 | .DS_Store
7 | /build
8 | /captures
9 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # HexagonView
2 | 六边形带圆角的自定义View,支持图文混排,点击区域,水平垂直方向切换,圆角大小等各种属性
3 |
4 | ### 联系方式
5 | * 邮箱地址: liaojeason@126.com
6 | * QQ群: 489873144 (建议使用QQ群,邮箱使用较少,可能看的不及时)
7 | * 本群刚建立,旨在为使用我的github项目的人提供方便,如果遇到问题欢迎在群里提问。个人能力也有限,希望一起学习一起进步。
8 |
9 |
10 | ## 演示
11 | 
12 |
13 | ## 1.用法
14 |
15 | 该项目和我github上其他的view相关的项目已经一起打包上传到jCenter仓库中(源码地址 [https://github.com/jeasonlzy0216/ViewCore](https://github.com/jeasonlzy0216/ViewCore) ),使用的时候可以直接使用compile依赖,用法如下
16 | ```java
17 | compile 'com.lzy.widget:view-core:0.1.9'
18 | ```
19 | 或者使用
20 | ```java
21 | compile project(':hexagonview')
22 | ```
23 |
24 | ## 2.注意
25 | 由于该控件不是矩形,所以想要点击事件只在六边形区域有效,需要设置`OnHexagonViewClickListener`,代码如下:
26 | ```java
27 | hexagon.setOnHexagonClickListener(new HexagonView.OnHexagonViewClickListener() {
28 | @Override
29 | public void onClick(View view) {
30 | Toast.makeText(HexagonActivity.this, "点击六边形了!", Toast.LENGTH_SHORT).show();
31 | }
32 | });
33 | ```
34 |
35 | ## 3.参数含义
36 |
37 |
38 |
39 |
40 | 自定义属性名字 |
41 | 参数含义 |
42 |
43 |
44 |
45 |
46 | hexagonText |
47 | 文字内容 |
48 |
49 |
50 | hexagonTextSize |
51 | 文字大小 |
52 |
53 |
54 | hexagonTextColor |
55 | 文字颜色 |
56 |
57 |
58 | hexagonBorderWidtd |
59 | 边框宽度 |
60 |
61 |
62 | hexagonBorderColor |
63 | 边框颜色 |
64 |
65 |
66 | hexagonFillColor |
67 | 背景填充色 |
68 |
69 |
70 | hexagonCorner |
71 | 圆角大小 |
72 |
73 |
74 | hexagonBreakLineCount |
75 | 换行字节数,一个中文代表两个字节 |
76 |
77 |
78 | hexagonMaxLine |
79 | 允许的最大行数,超过显示... |
80 |
81 |
82 | hexagonTextSpacing |
83 | 每行文本的间距 |
84 |
85 |
86 | hexagonBorderOverlay |
87 | 边框是否覆盖在背景之上 |
88 |
89 |
90 | hexagonOrientation |
91 | 六边的的方向,横向和 |
92 |
93 |
94 |
95 |
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | android {
4 | compileSdkVersion 23
5 | buildToolsVersion "23.0.2"
6 |
7 | defaultConfig {
8 | applicationId "com.lzy.hexagonviewdemo"
9 | minSdkVersion 8
10 | targetSdkVersion 22
11 | versionCode 1
12 | versionName "1.0"
13 | }
14 | buildTypes {
15 | release {
16 | minifyEnabled false
17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
18 | }
19 | }
20 | }
21 |
22 | dependencies {
23 | compile fileTree(include: ['*.jar'], dir: 'libs')
24 | compile 'com.android.support:appcompat-v7:23.1.1'
25 |
26 | // compile 'com.lzy.widget:view-core:0.1.9'
27 | compile 'com.lzy.utils:colorpicker:0.1.0'
28 | compile project(':hexagonview')
29 | }
30 |
--------------------------------------------------------------------------------
/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/lzy/hexagonviewdemo/ApplicationTest.java:
--------------------------------------------------------------------------------
1 | package com.lzy.hexagonviewdemo;
2 |
3 | import android.app.Application;
4 | import android.test.ApplicationTestCase;
5 |
6 | /**
7 | * Testing Fundamentals
8 | */
9 | public class ApplicationTest extends ApplicationTestCase {
10 | public ApplicationTest() {
11 | super(Application.class);
12 | }
13 | }
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/app/src/main/java/com/lzy/hexagonviewdemo/HexagonActivity.java:
--------------------------------------------------------------------------------
1 | package com.lzy.hexagonviewdemo;
2 |
3 | import android.content.Intent;
4 | import android.database.Cursor;
5 | import android.graphics.Bitmap;
6 | import android.graphics.BitmapFactory;
7 | import android.net.Uri;
8 | import android.os.Bundle;
9 | import android.provider.MediaStore;
10 | import android.support.v7.app.AppCompatActivity;
11 | import android.text.Editable;
12 | import android.text.TextWatcher;
13 | import android.view.View;
14 | import android.view.ViewGroup;
15 | import android.widget.Button;
16 | import android.widget.CheckBox;
17 | import android.widget.CompoundButton;
18 | import android.widget.EditText;
19 | import android.widget.RadioGroup;
20 | import android.widget.SeekBar;
21 | import android.widget.TextView;
22 | import android.widget.Toast;
23 |
24 | import com.lzy.colorpicker.ColorPickerDialog;
25 | import com.lzy.widget.HexagonView;
26 |
27 | public class HexagonActivity extends AppCompatActivity implements SeekBar.OnSeekBarChangeListener, RadioGroup.OnCheckedChangeListener, CompoundButton.OnCheckedChangeListener, TextWatcher, View.OnClickListener {
28 |
29 | private static final int SELECT_PIC_KITKAT = 0;
30 | private static final int SELECT_PIC = 1;
31 | private TextView tv_textSize;
32 | private TextView tv_borderWidth;
33 | private TextView tv_corner;
34 | private TextView tv_breakLineCount;
35 | private TextView tv_maxLine;
36 | private TextView tv_textSpacing;
37 | private TextView tv_width;
38 | private TextView tv_height;
39 | private TextView tv_padding;
40 | private HexagonView hexagon;
41 | ViewGroup.LayoutParams params;
42 |
43 | private int textColor = 0xFFFFFFFF;
44 | private int borderColor = 0x99FFFF00;
45 | private int fillColor = 0xFFFF0000;
46 | private int backColor = 0x00000000;
47 |
48 | @Override
49 | protected void onCreate(Bundle savedInstanceState) {
50 | super.onCreate(savedInstanceState);
51 | setContentView(R.layout.activity_hexagon);
52 |
53 | hexagon = (HexagonView) findViewById(R.id.hexagon);
54 |
55 | tv_textSize = (TextView) findViewById(R.id.tv_textSize);
56 | tv_borderWidth = (TextView) findViewById(R.id.tv_borderWidth);
57 | tv_corner = (TextView) findViewById(R.id.tv_corner);
58 | tv_breakLineCount = (TextView) findViewById(R.id.tv_breakLineCount);
59 | tv_maxLine = (TextView) findViewById(R.id.tv_maxLine);
60 | tv_textSpacing = (TextView) findViewById(R.id.tv_textSpacing);
61 | tv_width = (TextView) findViewById(R.id.tv_width);
62 | tv_height = (TextView) findViewById(R.id.tv_height);
63 | tv_padding = (TextView) findViewById(R.id.tv_padding);
64 |
65 | SeekBar sb_textSize = (SeekBar) findViewById(R.id.sb_textSize);
66 | sb_textSize.setOnSeekBarChangeListener(this);
67 | sb_textSize.setMax(50);
68 | sb_textSize.setProgress(20);
69 | SeekBar sb_borderWidth = (SeekBar) findViewById(R.id.sb_borderWidth);
70 | sb_borderWidth.setOnSeekBarChangeListener(this);
71 | sb_borderWidth.setMax(50);
72 | sb_borderWidth.setProgress(20);
73 | SeekBar sb_corner = (SeekBar) findViewById(R.id.sb_corner);
74 | sb_corner.setOnSeekBarChangeListener(this);
75 | sb_corner.setMax(50);
76 | sb_corner.setProgress(10);
77 | SeekBar sb_breakLineCount = (SeekBar) findViewById(R.id.sb_breakLineCount);
78 | sb_breakLineCount.setOnSeekBarChangeListener(this);
79 | sb_breakLineCount.setMax(15);
80 | sb_breakLineCount.setProgress(6);
81 | SeekBar sb_maxLine = (SeekBar) findViewById(R.id.sb_maxLine);
82 | sb_maxLine.setOnSeekBarChangeListener(this);
83 | sb_maxLine.setMax(5);
84 | sb_maxLine.setProgress(3);
85 | SeekBar sb_textSpacing = (SeekBar) findViewById(R.id.sb_textSpacing);
86 | sb_textSpacing.setOnSeekBarChangeListener(this);
87 | sb_textSpacing.setMax(20);
88 | sb_textSpacing.setProgress(0);
89 | SeekBar sb_width = (SeekBar) findViewById(R.id.sb_width);
90 | sb_width.setOnSeekBarChangeListener(this);
91 | sb_width.setMax(300);
92 | sb_width.setProgress(200);
93 | SeekBar sb_height = (SeekBar) findViewById(R.id.sb_height);
94 | sb_height.setOnSeekBarChangeListener(this);
95 | sb_height.setMax(300);
96 | sb_height.setProgress(200);
97 | SeekBar sb_padding = (SeekBar) findViewById(R.id.sb_padding);
98 | sb_padding.setOnSeekBarChangeListener(this);
99 | sb_padding.setMax(100);
100 | sb_padding.setProgress(10);
101 |
102 | RadioGroup orientation = (RadioGroup) findViewById(R.id.orientation);
103 | orientation.setOnCheckedChangeListener(this);
104 | orientation.check(R.id.vertical);
105 |
106 | CheckBox borderOverlay = (CheckBox) findViewById(R.id.borderOverlay);
107 | borderOverlay.setOnCheckedChangeListener(this);
108 | borderOverlay.setChecked(false);
109 | CheckBox isUseBitmap = (CheckBox) findViewById(R.id.isUseBitmap);
110 | isUseBitmap.setOnCheckedChangeListener(this);
111 | isUseBitmap.setChecked(true);
112 | CheckBox enableClick = (CheckBox) findViewById(R.id.enableClick);
113 | enableClick.setOnCheckedChangeListener(this);
114 | enableClick.setChecked(true);
115 |
116 | Button checkImage = (Button) findViewById(R.id.checkImage);
117 | checkImage.setOnClickListener(this);
118 | Button textColor = (Button) findViewById(R.id.textColor);
119 | textColor.setOnClickListener(this);
120 | Button borderColor = (Button) findViewById(R.id.borderColor);
121 | borderColor.setOnClickListener(this);
122 | Button fillColor = (Button) findViewById(R.id.fillColor);
123 | fillColor.setOnClickListener(this);
124 | Button backColor = (Button) findViewById(R.id.backColor);
125 | backColor.setOnClickListener(this);
126 |
127 | EditText text = (EditText) findViewById(R.id.text);
128 | text.addTextChangedListener(this);
129 | text.setText("ABC这是EFG测试文字H");
130 | text.selectAll();
131 | }
132 |
133 | @Override
134 | public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
135 | switch (seekBar.getId()) {
136 | case R.id.sb_textSize:
137 | hexagon.setTextSize(progress);
138 | tv_textSize.setText(progress + "sp");
139 | break;
140 | case R.id.sb_borderWidth:
141 | hexagon.setBorderWidth((int) (progress * getResources().getDisplayMetrics().density));
142 | tv_borderWidth.setText(progress + "dp");
143 | break;
144 | case R.id.sb_corner:
145 | hexagon.setCorner((int) (progress * getResources().getDisplayMetrics().density));
146 | tv_corner.setText(progress + "dp");
147 | break;
148 | case R.id.sb_breakLineCount:
149 | hexagon.setBreakLineCount(progress);
150 | tv_breakLineCount.setText(progress + "");
151 | break;
152 | case R.id.sb_maxLine:
153 | hexagon.setMaxLine(progress);
154 | tv_maxLine.setText(progress + "");
155 | break;
156 | case R.id.sb_textSpacing:
157 | hexagon.setTextSpacing((int) (progress * getResources().getDisplayMetrics().density));
158 | tv_textSpacing.setText(progress + "dp");
159 | break;
160 | case R.id.sb_width:
161 | params = hexagon.getLayoutParams();
162 | params.width = (int) (progress * getResources().getDisplayMetrics().density);
163 | hexagon.setLayoutParams(params);
164 | tv_width.setText(progress + "dp");
165 | break;
166 | case R.id.sb_height:
167 | params = hexagon.getLayoutParams();
168 | params.height = (int) (progress * getResources().getDisplayMetrics().density);
169 | hexagon.setLayoutParams(params);
170 | tv_height.setText(progress + "dp");
171 | break;
172 | case R.id.sb_padding:
173 | int padding = (int) (progress * getResources().getDisplayMetrics().density);
174 | hexagon.setPadding(padding, padding, padding, padding);
175 | tv_padding.setText(progress + "dp");
176 | break;
177 | }
178 | }
179 |
180 | @Override
181 | public void onStartTrackingTouch(SeekBar seekBar) {
182 |
183 | }
184 |
185 | @Override
186 | public void onStopTrackingTouch(SeekBar seekBar) {
187 |
188 | }
189 |
190 | @Override
191 | public void onCheckedChanged(RadioGroup group, int checkedId) {
192 | switch (checkedId) {
193 | case R.id.horizontal:
194 | hexagon.setHexagonOrientation(HexagonView.HORIZONTAL);
195 | break;
196 | case R.id.vertical:
197 | hexagon.setHexagonOrientation(HexagonView.VERTICAL);
198 | break;
199 | }
200 | }
201 |
202 | @Override
203 | public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
204 | switch (buttonView.getId()) {
205 | case R.id.borderOverlay:
206 | hexagon.setBorderOverlay(isChecked);
207 | break;
208 | case R.id.isUseBitmap:
209 | if (isChecked) {
210 | hexagon.setImageResource(R.mipmap.hexagon_image);
211 | } else {
212 | hexagon.setImageBitmap(null);
213 | }
214 | break;
215 | case R.id.enableClick:
216 | if (isChecked) {
217 | hexagon.setOnHexagonClickListener(new HexagonView.OnHexagonViewClickListener() {
218 | @Override
219 | public void onClick(View view) {
220 | Toast.makeText(HexagonActivity.this, "点击六边形了!", Toast.LENGTH_SHORT).show();
221 | }
222 | });
223 | } else {
224 | hexagon.setOnHexagonClickListener(null);
225 | }
226 | }
227 | }
228 |
229 | @Override
230 | public void beforeTextChanged(CharSequence s, int start, int count, int after) {
231 |
232 | }
233 |
234 | @Override
235 | public void onTextChanged(CharSequence s, int start, int before, int count) {
236 | hexagon.setText(s.toString());
237 | }
238 |
239 | @Override
240 | public void afterTextChanged(Editable s) {
241 |
242 | }
243 |
244 | @Override
245 | public void onClick(View v) {
246 | switch (v.getId()) {
247 | case R.id.textColor:
248 | ColorPickerDialog textDialog = new ColorPickerDialog(this, textColor);
249 | textDialog.setAlphaSliderVisible(true);
250 | textDialog.setHexValueEnabled(true);
251 | textDialog.show();
252 | textDialog.setOnColorChangedListener(new ColorPickerDialog.OnColorChangedListener() {
253 | @Override
254 | public void onColorChanged(int color) {
255 | textColor = color;
256 | hexagon.setTextColor(color);
257 | }
258 | });
259 | break;
260 | case R.id.borderColor:
261 | ColorPickerDialog borderDialog = new ColorPickerDialog(this, borderColor);
262 | borderDialog.setAlphaSliderVisible(true);
263 | borderDialog.setHexValueEnabled(true);
264 | borderDialog.show();
265 | borderDialog.setOnColorChangedListener(new ColorPickerDialog.OnColorChangedListener() {
266 | @Override
267 | public void onColorChanged(int color) {
268 | borderColor = color;
269 | hexagon.setBorderColor(color);
270 | }
271 | });
272 | break;
273 | case R.id.fillColor:
274 | ColorPickerDialog fillColorDialog = new ColorPickerDialog(this, fillColor);
275 | fillColorDialog.setAlphaSliderVisible(true);
276 | fillColorDialog.setHexValueEnabled(true);
277 | fillColorDialog.show();
278 | fillColorDialog.setOnColorChangedListener(new ColorPickerDialog.OnColorChangedListener() {
279 | @Override
280 | public void onColorChanged(int color) {
281 | fillColor = color;
282 | hexagon.setFillColor(color);
283 | }
284 | });
285 | break;
286 | case R.id.backColor:
287 | ColorPickerDialog backColorDialog = new ColorPickerDialog(this, backColor);
288 | backColorDialog.setAlphaSliderVisible(true);
289 | backColorDialog.setHexValueEnabled(true);
290 | backColorDialog.show();
291 | backColorDialog.setOnColorChangedListener(new ColorPickerDialog.OnColorChangedListener() {
292 | @Override
293 | public void onColorChanged(int color) {
294 | backColor = color;
295 | hexagon.setBackgroundColor(color);
296 | }
297 | });
298 | break;
299 | case R.id.checkImage:
300 | Intent intent = new Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
301 | startActivityForResult(intent, 101);
302 | break;
303 | }
304 | }
305 |
306 | @Override
307 | protected void onActivityResult(int requestCode, int resultCode, Intent data) {
308 | super.onActivityResult(requestCode, resultCode, data);
309 | if (null != data && requestCode == 101) {
310 | Uri selectedImage = data.getData(); // content://media/external/images/media/16753
311 | String path = selectedImage.getPath(); // /external/images/media/16753
312 | String[] filePathColumn = {MediaStore.Images.Media.DATA};
313 | Cursor cursor = getContentResolver().query(selectedImage, filePathColumn, null, null, null);
314 | System.out.println("cursor:" + cursor);
315 | if (cursor != null) {
316 | cursor.moveToFirst();
317 | int columnIndex = cursor.getColumnIndex(filePathColumn[0]);
318 | String picturePath = cursor.getString(columnIndex); // /storage/emulated/0/DCIM/Camera/1450292407465.jpg
319 | cursor.close();
320 | Bitmap bitmap = BitmapFactory.decodeFile(picturePath);
321 | hexagon.setImageBitmap(bitmap);
322 | }
323 | }
324 | }
325 | }
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_hexagon.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
11 |
12 |
19 |
20 |
23 |
24 |
27 |
28 |
33 |
34 |
35 |
36 |
37 |
40 |
41 |
44 |
45 |
50 |
51 |
52 |
53 |
54 |
57 |
58 |
61 |
62 |
67 |
68 |
69 |
70 |
71 |
74 |
75 |
78 |
79 |
84 |
85 |
86 |
87 |
88 |
91 |
92 |
95 |
96 |
101 |
102 |
103 |
104 |
105 |
108 |
109 |
112 |
113 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
130 |
131 |
136 |
137 |
142 |
143 |
150 |
151 |
158 |
159 |
160 |
161 |
166 |
167 |
174 |
175 |
182 |
183 |
190 |
191 |
192 |
196 |
197 |
203 |
204 |
224 |
225 |
226 |
231 |
232 |
238 |
239 |
244 |
245 |
250 |
251 |
256 |
257 |
262 |
263 |
264 |
265 |
270 |
271 |
278 |
279 |
282 |
283 |
286 |
287 |
292 |
293 |
294 |
295 |
296 |
299 |
300 |
303 |
304 |
309 |
310 |
311 |
312 |
313 |
316 |
317 |
320 |
321 |
326 |
327 |
328 |
329 |
330 |
331 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/hexagon_image.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jeasonlzy/HexagonView/730909317d5f6daec4a015a344f3d874ef658027/app/src/main/res/mipmap-hdpi/hexagon_image.jpg
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jeasonlzy/HexagonView/730909317d5f6daec4a015a344f3d874ef658027/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/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 | HexagonView
3 |
4 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/app/src/test/java/com/lzy/hexagonviewdemo/ExampleUnitTest.java:
--------------------------------------------------------------------------------
1 | package com.lzy.hexagonviewdemo;
2 |
3 | import org.junit.Test;
4 |
5 | import static org.junit.Assert.*;
6 |
7 | /**
8 | * To work on unit tests, switch the Test Artifact in the Build Variants view.
9 | */
10 | public class ExampleUnitTest {
11 | @Test
12 | public void addition_isCorrect() throws Exception {
13 | assertEquals(4, 2 + 2);
14 | }
15 | }
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 |
3 | buildscript {
4 | repositories {
5 | jcenter()
6 | }
7 | dependencies {
8 | classpath 'com.android.tools.build:gradle:2.0.0'
9 |
10 | // NOTE: Do not place your application dependencies here; they belong
11 | // in the individual module build.gradle files
12 | }
13 | }
14 |
15 | allprojects {
16 | repositories {
17 | jcenter()
18 | }
19 | }
20 |
21 | task clean(type: Delete) {
22 | delete rootProject.buildDir
23 | }
24 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 |
3 | # IDE (e.g. Android Studio) users:
4 | # Gradle settings configured through the IDE *will override*
5 | # any settings specified in this file.
6 |
7 | # For more details on how to configure your build environment visit
8 | # http://www.gradle.org/docs/current/userguide/build_environment.html
9 |
10 | # Specifies the JVM arguments used for the daemon process.
11 | # The setting is particularly useful for tweaking memory settings.
12 | # Default value: -Xmx10248m -XX:MaxPermSize=256m
13 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
14 |
15 | # When configured, Gradle will run in incubating parallel mode.
16 | # This option should only be used with decoupled projects. More details, visit
17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
18 | # org.gradle.parallel=true
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jeasonlzy/HexagonView/730909317d5f6daec4a015a344f3d874ef658027/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Mon Apr 25 10:12:04 CST 2016
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.10-all.zip
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # Attempt to set APP_HOME
46 | # Resolve links: $0 may be a link
47 | PRG="$0"
48 | # Need this for relative symlinks.
49 | while [ -h "$PRG" ] ; do
50 | ls=`ls -ld "$PRG"`
51 | link=`expr "$ls" : '.*-> \(.*\)$'`
52 | if expr "$link" : '/.*' > /dev/null; then
53 | PRG="$link"
54 | else
55 | PRG=`dirname "$PRG"`"/$link"
56 | fi
57 | done
58 | SAVED="`pwd`"
59 | cd "`dirname \"$PRG\"`/" >/dev/null
60 | APP_HOME="`pwd -P`"
61 | cd "$SAVED" >/dev/null
62 |
63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
64 |
65 | # Determine the Java command to use to start the JVM.
66 | if [ -n "$JAVA_HOME" ] ; then
67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
68 | # IBM's JDK on AIX uses strange locations for the executables
69 | JAVACMD="$JAVA_HOME/jre/sh/java"
70 | else
71 | JAVACMD="$JAVA_HOME/bin/java"
72 | fi
73 | if [ ! -x "$JAVACMD" ] ; then
74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
75 |
76 | Please set the JAVA_HOME variable in your environment to match the
77 | location of your Java installation."
78 | fi
79 | else
80 | JAVACMD="java"
81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
82 |
83 | Please set the JAVA_HOME variable in your environment to match the
84 | location of your Java installation."
85 | fi
86 |
87 | # Increase the maximum file descriptors if we can.
88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
89 | MAX_FD_LIMIT=`ulimit -H -n`
90 | if [ $? -eq 0 ] ; then
91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
92 | MAX_FD="$MAX_FD_LIMIT"
93 | fi
94 | ulimit -n $MAX_FD
95 | if [ $? -ne 0 ] ; then
96 | warn "Could not set maximum file descriptor limit: $MAX_FD"
97 | fi
98 | else
99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
100 | fi
101 | fi
102 |
103 | # For Darwin, add options to specify how the application appears in the dock
104 | if $darwin; then
105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
106 | fi
107 |
108 | # For Cygwin, switch paths to Windows format before running java
109 | if $cygwin ; then
110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
112 | JAVACMD=`cygpath --unix "$JAVACMD"`
113 |
114 | # We build the pattern for arguments to be converted via cygpath
115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
116 | SEP=""
117 | for dir in $ROOTDIRSRAW ; do
118 | ROOTDIRS="$ROOTDIRS$SEP$dir"
119 | SEP="|"
120 | done
121 | OURCYGPATTERN="(^($ROOTDIRS))"
122 | # Add a user-defined pattern to the cygpath arguments
123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
125 | fi
126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
127 | i=0
128 | for arg in "$@" ; do
129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
131 |
132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
134 | else
135 | eval `echo args$i`="\"$arg\""
136 | fi
137 | i=$((i+1))
138 | done
139 | case $i in
140 | (0) set -- ;;
141 | (1) set -- "$args0" ;;
142 | (2) set -- "$args0" "$args1" ;;
143 | (3) set -- "$args0" "$args1" "$args2" ;;
144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
150 | esac
151 | fi
152 |
153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
154 | function splitJvmOpts() {
155 | JVM_OPTS=("$@")
156 | }
157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
159 |
160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
161 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/hexagonview/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/hexagonview/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 |
3 | android {
4 | compileSdkVersion 23
5 | buildToolsVersion "23.0.2"
6 |
7 | defaultConfig {
8 | minSdkVersion 8
9 | targetSdkVersion 22
10 | versionCode 1
11 | versionName "1.0"
12 | }
13 | buildTypes {
14 | release {
15 | minifyEnabled false
16 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
17 | }
18 | }
19 | }
20 |
21 | dependencies {
22 | compile fileTree(include: ['*.jar'], dir: 'libs')
23 | compile 'com.android.support:support-annotations:23.1.1'
24 | }
25 |
--------------------------------------------------------------------------------
/hexagonview/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 |
--------------------------------------------------------------------------------
/hexagonview/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/hexagonview/src/main/java/com/lzy/widget/HexagonView.java:
--------------------------------------------------------------------------------
1 | package com.lzy.widget;
2 |
3 | import android.content.Context;
4 | import android.content.res.TypedArray;
5 | import android.graphics.Bitmap;
6 | import android.graphics.BitmapShader;
7 | import android.graphics.Canvas;
8 | import android.graphics.ColorFilter;
9 | import android.graphics.CornerPathEffect;
10 | import android.graphics.Matrix;
11 | import android.graphics.Paint;
12 | import android.graphics.Path;
13 | import android.graphics.PointF;
14 | import android.graphics.Shader;
15 | import android.graphics.drawable.BitmapDrawable;
16 | import android.graphics.drawable.ColorDrawable;
17 | import android.graphics.drawable.Drawable;
18 | import android.net.Uri;
19 | import android.support.annotation.ColorInt;
20 | import android.support.annotation.ColorRes;
21 | import android.support.annotation.DrawableRes;
22 | import android.support.annotation.StringRes;
23 | import android.text.TextUtils;
24 | import android.util.AttributeSet;
25 | import android.util.TypedValue;
26 | import android.view.MotionEvent;
27 | import android.view.View;
28 | import android.view.animation.Animation;
29 | import android.view.animation.AnimationSet;
30 | import android.view.animation.ScaleAnimation;
31 | import android.widget.ImageView;
32 |
33 | import java.util.ArrayList;
34 | import java.util.List;
35 |
36 | /**
37 | * ================================================
38 | * 作 者:廖子尧
39 | * 版 本:1.0
40 | * 创建日期:2015/12/15
41 | * 描 述:
42 | * 修订历史:
43 | * ================================================
44 | */
45 | public class HexagonView extends ImageView {
46 |
47 | private static final double RADIAN30 = Math.PI / 6.0; //30 弧度
48 | private static final ScaleType SCALE_TYPE = ScaleType.CENTER_CROP; //只允许CENTER_CROP模式
49 | private static final Bitmap.Config BITMAP_CONFIG = Bitmap.Config.ARGB_8888; //默认创建的格式
50 | private static final int COLOR_DRAWABLE_DIMENSION = 2; //对于 colorDrawable 的大小
51 | public static final int HORIZONTAL = 0; //水平放置
52 | public static final int VERTICAL = 1; //竖直放置
53 |
54 | //以下是自定义属性
55 | private String text = ""; // 文字
56 | private int textSize = 12; // 文字大小,单位sp
57 | private int textColor = 0xFFFFFFFF; // 文字颜色
58 | private int borderWidth = 2; // 边框宽度,单位 dp
59 | private int borderColor = 0xFFFF0000; // 边框颜色
60 | private int fillColor = 0xFF0000FF; // 背景颜色
61 | private int corner = 10; // 圆角度数,单位 dp
62 | private int breakLineCount = 4; // 文字换行长度,单位 字节, 一个汉字占两个字节
63 | private int maxLine = 3; // 最多的行数,大于等于两行时,最后一行显示 "..."
64 | private int textSpacing = 4; // 两行文字的间距,只有一行文字时无效,单位 dp
65 | private int hexagonOrientation = VERTICAL; // 默认显示的文字
66 | private boolean borderOverlay = false; // true表示边框会覆盖一部分图片,false表示边框不会覆盖在图片之上
67 |
68 | //以下是成员变量
69 | private final Matrix mShaderMatrix = new Matrix(); //对图片缩放的矩阵
70 | private final Paint mBitmapPaint = new Paint(); //图片的画笔
71 | private final Paint mTextPaint = new Paint(); //只有文字时文字的画笔
72 | private final Paint mTextBitmapPaint = new Paint(); //只有文字时文字的画笔
73 | private final Paint mBorderPaint = new Paint(); //边框的画笔
74 | private final Paint mFillPaint = new Paint(); //背景色的画笔
75 | private List lineList = new ArrayList<>(); //每行文字的集合
76 | private List textBaseYList = new ArrayList<>(); //每行文字的BaseLine的集合
77 | private BitmapShader mBitmapShader; //用于绘制性状的 BitmapShader
78 | private Bitmap mBitmap; //设置的图片
79 | private ColorFilter mColorFilter; //滤色
80 | private Path mDrawPath; //可以绘制的路径
81 | private Path mBorderPath; //绘制边框的路径
82 | private Path mBitmapPath; //绘制图片的路径
83 |
84 | private List mDrawPathPointList; //最外边六边形的所有点的集合
85 | private List mBitmapPointList; //最内边六边形的所有点的集合
86 | private float mHexagonWidth; //六边形的真实宽
87 | private float mHexagonHeight; //六边形的真实高
88 | private float mTranslateX; //画布需要在X反向平移的距离
89 | private float mTranslateY; //画布需要在Y方向平移的距离
90 | private AnimationSet mAnimationSet; //点击时执行动画的集合
91 | private OnHexagonViewClickListener mListener; //点击六边形的监听
92 | private boolean isLasso = false; //是否点中多边形
93 | private LassoUtils mLasso; //判断点击点是否在多边形内部的工具类
94 | private boolean isNeedMore; //是否需要添加三个点
95 |
96 | public interface OnHexagonViewClickListener {
97 | void onClick(View view);
98 | }
99 |
100 | public void setOnHexagonClickListener(OnHexagonViewClickListener listener) {
101 | this.mListener = listener;
102 | }
103 |
104 | public HexagonView(Context context) {
105 | this(context, null);
106 | }
107 |
108 | public HexagonView(Context context, AttributeSet attrs) {
109 | this(context, attrs, 0);
110 | }
111 |
112 | public HexagonView(Context context, AttributeSet attrs, int defStyleAttr) {
113 | super(context, attrs, defStyleAttr);
114 |
115 | textSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, textSize, getResources().getDisplayMetrics());
116 | borderWidth = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, borderWidth, getResources().getDisplayMetrics());
117 | corner = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, corner, getResources().getDisplayMetrics());
118 | textSpacing = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, textSpacing, getResources().getDisplayMetrics());
119 |
120 | TypedArray typeA = context.obtainStyledAttributes(attrs, R.styleable.HexagonView);
121 | text = typeA.getString(R.styleable.HexagonView_hexagonText);
122 | textSize = typeA.getDimensionPixelSize(R.styleable.HexagonView_hexagonTextSize, textSize);
123 | textColor = typeA.getColor(R.styleable.HexagonView_hexagonTextColor, textColor);
124 | borderWidth = typeA.getDimensionPixelSize(R.styleable.HexagonView_hexagonBorderWidth, borderWidth);
125 | borderColor = typeA.getColor(R.styleable.HexagonView_hexagonBorderColor, borderColor);
126 | fillColor = typeA.getColor(R.styleable.HexagonView_hexagonFillColor, fillColor);
127 | corner = typeA.getDimensionPixelSize(R.styleable.HexagonView_hexagonCorner, corner);
128 | breakLineCount = typeA.getInt(R.styleable.HexagonView_hexagonBreakLineCount, breakLineCount);
129 | maxLine = typeA.getInt(R.styleable.HexagonView_hexagonMaxLine, maxLine);
130 | textSpacing = typeA.getDimensionPixelSize(R.styleable.HexagonView_hexagonTextSpacing, textSpacing);
131 | borderOverlay = typeA.getBoolean(R.styleable.HexagonView_hexagonBorderOverlay, borderOverlay);
132 | hexagonOrientation = typeA.getInt(R.styleable.HexagonView_hexagonOrientation, hexagonOrientation);
133 | typeA.recycle();
134 |
135 | //默认不允许设置外部点击事件,如果要点击,需要使用OnHexagonViewClickListener接口
136 | setClickable(false);
137 | //初始化对触摸点坐标判断的方法
138 | mLasso = LassoUtils.getInstance();
139 | initAnimation();
140 | }
141 |
142 | /**
143 | * 初始化补间动画
144 | */
145 | private void initAnimation() {
146 | float start = 1.0f;
147 | float end = 0.9f;
148 | ScaleAnimation startAnimation = new ScaleAnimation(start, end, start, end, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
149 | startAnimation.setDuration(30);
150 | ScaleAnimation endAnimation = new ScaleAnimation(end, start, end, start, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
151 | endAnimation.setDuration(30);
152 | mAnimationSet = new AnimationSet(false);
153 | mAnimationSet.addAnimation(startAnimation);
154 | mAnimationSet.addAnimation(endAnimation);
155 | }
156 |
157 | @Override
158 | public boolean onTouchEvent(MotionEvent event) {
159 | switch (event.getAction()) {
160 | case MotionEvent.ACTION_DOWN:
161 | // 判断是否点中
162 | isLasso = mLasso.contains(event.getX(), event.getY());
163 | break;
164 | case MotionEvent.ACTION_MOVE:
165 | //如果滑出表示取消点击
166 | isLasso = mLasso.contains(event.getX(), event.getY());
167 | break;
168 | case MotionEvent.ACTION_UP:
169 | // View 中复制过来的代码,用于检测是否是点击动作,例如可能出现,按下后向上滑动,此时应不响应点击事件
170 | boolean focusTaken = false;
171 | if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
172 | focusTaken = requestFocus();
173 | }
174 | //如果选中,就执行点击事件
175 | if (!focusTaken && isLasso && mListener != null) {
176 | mListener.onClick(this);
177 | startAnimation(mAnimationSet);
178 | }
179 | isLasso = false;
180 | break;
181 | default:
182 | isLasso = false;
183 | break;
184 | }
185 | return true;
186 | }
187 |
188 | @Override
189 | protected void onSizeChanged(int w, int h, int oldw, int oldh) {
190 | super.onSizeChanged(w, h, oldw, oldh);
191 | setup();
192 | }
193 |
194 | private void setup() {
195 | if (getWidth() == 0 && getHeight() == 0) return;
196 |
197 | int drawWidth = Math.max(getWidth() - getPaddingLeft() - getPaddingRight(), 0);
198 | int drawHeight = Math.max(getHeight() - getPaddingTop() - getPaddingBottom(), 0);
199 | mTranslateX = getPaddingLeft();
200 | mTranslateY = getPaddingTop();
201 | if (hexagonOrientation == VERTICAL) {
202 | //竖直方向 高除以宽 大于 2除以根号3 表示宽度不足,这时以宽度为基准,否则以高度为基准
203 | if ((drawHeight * 1.0f / drawWidth) > (2.0f / Math.sqrt(3.0))) {
204 | mHexagonWidth = drawWidth;
205 | mHexagonHeight = (float) (2.0f * drawWidth / Math.sqrt(3.0));
206 | mTranslateY = (drawHeight - mHexagonHeight) / 2 + getPaddingTop();
207 | } else {
208 | mHexagonWidth = (float) (Math.sqrt(3.0) / 2 * drawHeight);
209 | mHexagonHeight = drawHeight;
210 | mTranslateX = (drawWidth - mHexagonWidth) / 2 + getPaddingLeft();
211 | }
212 | } else if (hexagonOrientation == HORIZONTAL) {
213 | //水平方向 宽除以高 大于 2除以根号3 表示高度不足,这时以高度为基准,否则以宽度为基准
214 | if ((drawWidth * 1.0f / drawHeight) > (2.0f / Math.sqrt(3.0))) {
215 | mHexagonWidth = (float) (2.0f * drawHeight / Math.sqrt(3.0));
216 | mHexagonHeight = drawHeight;
217 | mTranslateX = (drawWidth - mHexagonWidth) / 2 + getPaddingLeft();
218 | } else {
219 | mHexagonWidth = drawWidth;
220 | mHexagonHeight = (float) (Math.sqrt(3.0) / 2 * drawWidth);
221 | mTranslateY = (drawHeight - mHexagonHeight) / 2 + getPaddingTop();
222 | }
223 | }
224 |
225 | CornerPathEffect cornerPathEffect = new CornerPathEffect(corner);
226 |
227 | mBitmapPaint.setAntiAlias(true);
228 | mBitmapPaint.setPathEffect(cornerPathEffect);
229 |
230 | mTextBitmapPaint.setAntiAlias(true);
231 | mTextBitmapPaint.setPathEffect(cornerPathEffect);
232 |
233 | mBorderPaint.setStyle(Paint.Style.STROKE);
234 | mBorderPaint.setAntiAlias(true);
235 | mBorderPaint.setColor(borderColor);
236 | mBorderPaint.setStrokeWidth(borderWidth);
237 | mBorderPaint.setPathEffect(cornerPathEffect);
238 |
239 | mFillPaint.setStyle(Paint.Style.FILL);
240 | mFillPaint.setAntiAlias(true);
241 | mFillPaint.setColor(fillColor);
242 | mFillPaint.setPathEffect(cornerPathEffect);
243 |
244 | mTextPaint.setAntiAlias(true);
245 | mTextPaint.setColor(textColor);
246 | mTextPaint.setTextSize(textSize);
247 | mTextPaint.setTextAlign(Paint.Align.CENTER);
248 |
249 | mDrawPath = getHexagonDrawPath(mHexagonWidth, mHexagonHeight);
250 | mBorderPath = getBorderPath(mDrawPathPointList, borderWidth);
251 | mBitmapPath = getBitmapPath(mDrawPathPointList, borderWidth);
252 |
253 | //以下是对文字进行换行
254 | if (TextUtils.isEmpty(text)) text = "";
255 | lineList.clear();
256 | breakTextLine(text, breakLineCount, maxLine - 1);
257 | if (isNeedMore) lineList.add("...");
258 | textBaseYList = getBaseLineList(lineList.size());
259 |
260 | //获取文字的bitmap
261 | Bitmap textBitmap = getTextBitmap(lineList);
262 | if (textBitmap != null) {
263 | BitmapShader textBitmapShader = new BitmapShader(textBitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
264 | mTextBitmapPaint.setShader(textBitmapShader);
265 | }
266 |
267 | if (mBitmap != null) {
268 | mBitmapShader = new BitmapShader(mBitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
269 | mBitmapPaint.setShader(mBitmapShader);
270 | if (borderOverlay) updateShaderMatrix(mBitmap, mHexagonWidth, mHexagonHeight);
271 | else {
272 | if (hexagonOrientation == VERTICAL)
273 | updateShaderMatrix(mBitmap, mBitmapPointList.get(1).x - mBitmapPointList.get(5).x, mBitmapPointList.get(3).y - mBitmapPointList.get(0).y);
274 | if (hexagonOrientation == HORIZONTAL)
275 | updateShaderMatrix(mBitmap, mBitmapPointList.get(3).x - mBitmapPointList.get(0).x, mBitmapPointList.get(5).y - mBitmapPointList.get(1).y);
276 | }
277 | }
278 | mLasso.setLassoList(mDrawPathPointList);
279 |
280 | //通知界面重绘
281 | invalidate();
282 | }
283 |
284 | /**
285 | * 根据文字的行数,得到绘制有文字的 bitmap,为了方便用path切割图片,所以先把文字转成了bitmap
286 | */
287 | private Bitmap getTextBitmap(List lineList) {
288 | if (mHexagonWidth <= 0 || mHexagonHeight <= 0) return null;
289 | Bitmap target = Bitmap.createBitmap((int) mHexagonWidth, (int) mHexagonHeight, BITMAP_CONFIG);
290 | Canvas canvas = new Canvas(target);
291 | for (int i = 0; i < lineList.size(); i++) {
292 | canvas.drawText(lineList.get(i), mHexagonWidth / 2, textBaseYList.get(i), mTextPaint); //画文字
293 | }
294 | return target;
295 | }
296 |
297 | /**
298 | * 迭代法截断字符串
299 | *
300 | * @param text 需要换行的文本
301 | * @param breakLineCount 每行显示的最大数
302 | * @param maxLine 最多显示的行数
303 | */
304 | public void breakTextLine(String text, int breakLineCount, int maxLine) {
305 | if (TextUtils.isEmpty(text)) text = "";
306 | String line = getSubString(text, 0, breakLineCount);
307 | //只有一行的时候
308 | if (maxLine == 0) {
309 | isNeedMore = false;
310 | lineList.add(line);
311 | return;
312 | }
313 | //显示两行及其以上的时候
314 | if (lineList.size() < maxLine) {
315 | if (getWordCount(text) > breakLineCount) {
316 | isNeedMore = true;
317 | lineList.add(line);
318 | String otherString = getSubString(text, getWordCount(line), getWordCount(text));
319 | breakTextLine(otherString, breakLineCount, maxLine);
320 | } else {
321 | isNeedMore = false;
322 | lineList.add(line);
323 | }
324 | }
325 | }
326 |
327 | /**
328 | * 当最后一行是 "..." 的时候,让最后这行只占一半的字体高度,视觉上好看点
329 | *
330 | * @param lines 一共的行数
331 | * @return 每行文字的 baseLineY 的集合
332 | */
333 | private List getBaseLineList(int lines) {
334 | Paint.FontMetrics fontMetrics = mTextPaint.getFontMetrics();
335 | float fontHeight = fontMetrics.bottom - fontMetrics.top;
336 | float textBaseY = mHexagonHeight - (mHexagonHeight - fontHeight) / 2 - fontMetrics.bottom;
337 |
338 | List list = new ArrayList<>();
339 | int offset = -(lines - 1);
340 | for (int i = 0; i < lines; i++) {
341 | //当最后一行是点的时候,除最后一行外整体向下平移 fontHeight/4,最后一行向上平移 fontHeight/4
342 | if (isNeedMore) {
343 | if (i != lines - 1) {
344 | //不是最后一行,向下平移 fontHeight/4
345 | list.add(textBaseY + offset * (fontHeight / 2 + textSpacing / 2) + fontHeight / 4);
346 | offset += 2;
347 | } else {
348 | //最后一行,向上平移 fontHeight/4
349 | list.add(textBaseY + offset * (fontHeight / 2 + textSpacing / 2) - fontHeight / 4);
350 | }
351 | } else {
352 | list.add(textBaseY + offset * (fontHeight / 2 + textSpacing / 2));
353 | offset += 2;
354 | }
355 | }
356 | return list;
357 | }
358 |
359 | /**
360 | * 将 bitmap 按照 目标 width 和 height 缩放,并且平移到中间
361 | * 效果等效于 ImageView 的 CENTER_CROP
362 | */
363 | private void updateShaderMatrix(Bitmap bitmap, float width, float height) {
364 | float scale, dx = 0, dy = 0;
365 |
366 | int bitmapWidth = bitmap.getWidth();
367 | int bitmapHeight = bitmap.getHeight();
368 |
369 | mShaderMatrix.set(null);
370 | if (bitmapWidth * height > width * bitmapHeight) {
371 | scale = height / (float) bitmapHeight; //图片的宽高比 大于 有效绘制区域的宽高比,此时缩放比以 高度的缩放比为基准
372 | dx = (width - bitmapWidth * scale) * 0.5f; //dx 为负值,表示向左平移
373 | } else {
374 | scale = width / (float) bitmapWidth; //图片的宽高比 小于 有效绘制区域的宽高比,此时缩放比以 宽度的缩放比为基准
375 | dy = (height - bitmapHeight * scale) * 0.5f;//dy 为负值,表示向上平移
376 | }
377 | //设置图片的缩放大小
378 | //注意: matrix 的表象的像一个队列,pre 方法总是在队头插,post 方法总是在队尾插,set 方法总是在中间插
379 | //所以一般情况下,post 基本可以满足所有需求
380 | mShaderMatrix.postScale(scale, scale);
381 | //设置图片的平移距离
382 | if (!borderOverlay) {
383 | //将图片根据边框的大小右和下偏移
384 | if (hexagonOrientation == HORIZONTAL) {
385 | dx = (float) (dx + 0.5f + borderWidth / Math.cos(RADIAN30));
386 | dy = dy + 0.5f + borderWidth;
387 | }
388 | if (hexagonOrientation == VERTICAL) {
389 | dx = dx + 0.5f + borderWidth;
390 | dy = (float) (dy + 0.5f + borderWidth / Math.cos(RADIAN30));
391 | }
392 | mShaderMatrix.postTranslate((int) (dx + 0.5f), (int) (dy + 0.5f));
393 | } else {
394 | mShaderMatrix.postTranslate((int) (dx + 0.5f), (int) (dy + 0.5f));
395 | }
396 | mBitmapShader.setLocalMatrix(mShaderMatrix); //最后赋值给BitmapShader
397 | }
398 |
399 | @Override
400 | protected void onDraw(Canvas canvas) {
401 | // 有效的绘制区域已经没有了,就不绘制了
402 | if (hexagonOrientation == VERTICAL && (mBitmapPointList.get(1).x - mBitmapPointList.get(5).x <= 0)) return;
403 | if (hexagonOrientation == HORIZONTAL && (mBitmapPointList.get(5).y - mBitmapPointList.get(1).y <= 0)) return;
404 |
405 | canvas.translate(mTranslateX, mTranslateY); //平移画布,保证居中显示
406 | if (mBitmap == null) canvas.drawPath(mBitmapPath, mFillPaint); //画背景色,只在没有图片的时候画
407 | else canvas.drawPath(mBitmapPath, mBitmapPaint); //画背景图片
408 | canvas.drawPath(mBitmapPath, mTextBitmapPaint); //将文字转换成bitmap,方便用path切割
409 | if (borderWidth > 0) canvas.drawPath(mBorderPath, mBorderPaint); //画边框
410 | }
411 |
412 | /**
413 | * 构造六边形的可绘制区域
414 | */
415 | public Path getHexagonDrawPath(float width, float height) {
416 | Path path = new Path();
417 | mDrawPathPointList = new ArrayList<>();
418 | if (hexagonOrientation == VERTICAL) {
419 | path.moveTo(width / 2, 0);
420 | path.lineTo(width, height / 4);
421 | path.lineTo(width, height * 3 / 4);
422 | path.lineTo(width / 2, height);
423 | path.lineTo(0, height * 3 / 4);
424 | path.lineTo(0, height / 4);
425 | path.close();
426 | mDrawPathPointList.add(new PointF(width / 2, 0));
427 | mDrawPathPointList.add(new PointF(width, height / 4));
428 | mDrawPathPointList.add(new PointF(width, height * 3 / 4));
429 | mDrawPathPointList.add(new PointF(width / 2, height));
430 | mDrawPathPointList.add(new PointF(0, height * 3 / 4));
431 | mDrawPathPointList.add(new PointF(0, height / 4));
432 | }
433 | if (hexagonOrientation == HORIZONTAL) {
434 | path.moveTo(0, height / 2);
435 | path.lineTo(width / 4, 0);
436 | path.lineTo(width * 3 / 4, 0);
437 | path.lineTo(width, height / 2);
438 | path.lineTo(width * 3 / 4, height);
439 | path.lineTo(width / 4, height);
440 | path.close();
441 | mDrawPathPointList.add(new PointF(0, height / 2));
442 | mDrawPathPointList.add(new PointF(width / 4, 0));
443 | mDrawPathPointList.add(new PointF(width * 3 / 4, 0));
444 | mDrawPathPointList.add(new PointF(width, height / 2));
445 | mDrawPathPointList.add(new PointF(width * 3 / 4, height));
446 | mDrawPathPointList.add(new PointF(width / 4, height));
447 | }
448 | return path;
449 | }
450 |
451 | /**
452 | * 根据六边形绘制区域的大小和边框大小,得到边框的绘制区域
453 | */
454 | private Path getBorderPath(List pointList, int borderWidth) {
455 | //为避免计算误差,小于等于0 的时候,直接使用外层边框
456 | if (borderWidth <= 0) return mDrawPath;
457 | if (hexagonOrientation == VERTICAL) return makeVerticalHexagonPath(pointList, borderWidth / 2.0f, null);
458 | if (hexagonOrientation == HORIZONTAL) return makeHorizontalHexagonPath(pointList, borderWidth / 2.0f, null);
459 | return null;
460 | }
461 |
462 | /**
463 | * 根据六边形绘制区域的大小和边框大小,得到图片的绘制区域
464 | */
465 | private Path getBitmapPath(List pointList, int borderWidth) {
466 | //为避免计算误差,小于等于0 的时候,直接使用外层边框
467 | if (borderWidth <= 0) return mDrawPath;
468 | //true表示边框会覆盖一部分图片,false表示边框不会覆盖在图片之上
469 | if (borderOverlay) return mDrawPath;
470 | mBitmapPointList = new ArrayList<>();
471 | if (hexagonOrientation == VERTICAL) return makeVerticalHexagonPath(pointList, borderWidth, mBitmapPointList);
472 | if (hexagonOrientation == HORIZONTAL)
473 | return makeHorizontalHexagonPath(pointList, borderWidth, mBitmapPointList);
474 | return null;
475 | }
476 |
477 | /**
478 | * 对于竖直方向的六边形进行 offset 缩放偏移
479 | */
480 | private Path makeVerticalHexagonPath(List pointList, float offset, List pointFList) {
481 | Path path = new Path();
482 | path.moveTo(pointList.get(0).x, (float) (pointList.get(0).y + offset / Math.cos(RADIAN30)));
483 | path.lineTo(pointList.get(1).x - offset, (float) (pointList.get(1).y + offset * Math.tan(RADIAN30)));
484 | path.lineTo(pointList.get(2).x - offset, (float) (pointList.get(2).y - offset * Math.tan(RADIAN30)));
485 | path.lineTo(pointList.get(3).x, (float) (pointList.get(3).y - offset / Math.cos(RADIAN30)));
486 | path.lineTo(pointList.get(4).x + offset, (float) (pointList.get(4).y - offset * Math.tan(RADIAN30)));
487 | path.lineTo(pointList.get(5).x + offset, (float) (pointList.get(5).y + offset * Math.tan(RADIAN30)));
488 | path.close();
489 | if (pointFList != null) {
490 | pointFList.add(new PointF(pointList.get(0).x, (float) (pointList.get(0).y + offset / Math.cos(RADIAN30))));
491 | pointFList.add(new PointF(pointList.get(1).x - offset, (float) (pointList.get(1).y + offset * Math.tan(RADIAN30))));
492 | pointFList.add(new PointF(pointList.get(2).x - offset, (float) (pointList.get(2).y - offset * Math.tan(RADIAN30))));
493 | pointFList.add(new PointF(pointList.get(3).x, (float) (pointList.get(3).y - offset / Math.cos(RADIAN30))));
494 | pointFList.add(new PointF(pointList.get(4).x + offset, (float) (pointList.get(4).y - offset * Math.tan(RADIAN30))));
495 | pointFList.add(new PointF(pointList.get(5).x + offset, (float) (pointList.get(5).y + offset * Math.tan(RADIAN30))));
496 | }
497 | return path;
498 | }
499 |
500 | /**
501 | * 对于水平方向的六边形进行 offset 缩放偏移
502 | */
503 | private Path makeHorizontalHexagonPath(List pointList, float offset, List pointFList) {
504 | Path path = new Path();
505 | path.moveTo((float) (pointList.get(0).x + offset / Math.cos(RADIAN30)), pointList.get(0).y);
506 | path.lineTo((float) (pointList.get(1).x + offset * Math.tan(RADIAN30)), pointList.get(1).y + offset);
507 | path.lineTo((float) (pointList.get(2).x - offset * Math.tan(RADIAN30)), pointList.get(2).y + offset);
508 | path.lineTo((float) (pointList.get(3).x - offset / Math.cos(RADIAN30)), pointList.get(3).y);
509 | path.lineTo((float) (pointList.get(4).x - offset * Math.tan(RADIAN30)), pointList.get(4).y - offset);
510 | path.lineTo((float) (pointList.get(5).x + offset * Math.tan(RADIAN30)), pointList.get(5).y - offset);
511 | path.close();
512 | if (pointFList != null) {
513 | pointFList.add(new PointF((float) (pointList.get(0).x + offset / Math.cos(RADIAN30)), pointList.get(0).y));
514 | pointFList.add(new PointF((float) (pointList.get(1).x + offset * Math.tan(RADIAN30)), pointList.get(1).y + offset));
515 | pointFList.add(new PointF((float) (pointList.get(2).x - offset * Math.tan(RADIAN30)), pointList.get(2).y + offset));
516 | pointFList.add(new PointF((float) (pointList.get(3).x - offset / Math.cos(RADIAN30)), pointList.get(3).y));
517 | pointFList.add(new PointF((float) (pointList.get(4).x - offset * Math.tan(RADIAN30)), pointList.get(4).y - offset));
518 | pointFList.add(new PointF((float) (pointList.get(5).x + offset * Math.tan(RADIAN30)), pointList.get(5).y - offset));
519 | }
520 | return path;
521 | }
522 |
523 | /**
524 | * 将传入的drawable转换成bitmap
525 | */
526 | private Bitmap getBitmapFromDrawable(Drawable drawable) {
527 | if (drawable == null) return null;
528 | if (drawable instanceof BitmapDrawable) return ((BitmapDrawable) drawable).getBitmap();
529 |
530 | try {
531 | Bitmap bitmap;
532 | if (drawable instanceof ColorDrawable) {
533 | bitmap = Bitmap.createBitmap(COLOR_DRAWABLE_DIMENSION, COLOR_DRAWABLE_DIMENSION, BITMAP_CONFIG);
534 | } else {
535 | bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), BITMAP_CONFIG);
536 | }
537 | Canvas canvas = new Canvas(bitmap);
538 | drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
539 | drawable.draw(canvas);
540 | return bitmap;
541 | } catch (Exception e) {
542 | e.printStackTrace();
543 | return null;
544 | }
545 | }
546 |
547 | /**
548 | * @param str 需要截取的字符串
549 | * @param length 长度按字节数的长度计算,一个汉字为两个字节,例如需要截取两个汉字的长度,length = 4
550 | * @return 截取的字符串,不会出现半个字符
551 | */
552 | public String getSubString(String str, int length) {
553 | if (length < 0) return "";
554 | int count = 0, offset;
555 | char[] c = str.toCharArray();
556 | for (int i = 0; i < c.length; i++) {
557 | if (c[i] > 256) {
558 | offset = 2;
559 | count += 2;
560 | } else {
561 | offset = 1;
562 | count++;
563 | }
564 | if (count == length) return str.substring(0, i + 1);
565 | if ((count == length + 1 && offset == 2)) return str.substring(0, i);
566 | }
567 | return "";
568 | }
569 |
570 | /**
571 | * @param str 需要截取的字符串
572 | * @param from 开始截取的位置,按字节数算
573 | * @param to 结束截取的位置,按字节数算
574 | * @return 截取的字符串
575 | */
576 | public String getSubString(String str, int from, int to) {
577 | if (from < 0) from = 0;
578 | if (to > getWordCount(str)) to = getWordCount(str);
579 | String toString = getSubString(str, to);
580 | String fromString = getSubString(str, from);
581 | return toString.substring(fromString.length());
582 | }
583 |
584 | /**
585 | * 获得字符串长度,一个汉字占用两个字节
586 | */
587 | public int getWordCount(String s) {
588 | int length = 0;
589 | for (int i = 0; i < s.length(); i++) {
590 | int ascii = Character.codePointAt(s, i);
591 | if (ascii >= 0 && ascii <= 255) length++;
592 | else length += 2;
593 | }
594 | return length;
595 | }
596 |
597 | /*--------------------------------------------------------------------------------------------*/
598 | /*----------------------------------以下为重写的方法--------------------------------------------*/
599 | /*--------------------------------------------------------------------------------------------*/
600 | @Override
601 | public ScaleType getScaleType() {
602 | return SCALE_TYPE;
603 | }
604 |
605 | @Override
606 | public void setScaleType(ScaleType scaleType) {
607 | if (scaleType != SCALE_TYPE) {
608 | throw new IllegalArgumentException(String.format("ScaleType %s not supported.", scaleType));
609 | }
610 | }
611 |
612 | @Override
613 | public void setAdjustViewBounds(boolean adjustViewBounds) {
614 | if (adjustViewBounds) {
615 | throw new IllegalArgumentException("adjustViewBounds not supported.");
616 | }
617 | }
618 |
619 | @Override
620 | public void setImageBitmap(Bitmap bm) {
621 | super.setImageBitmap(bm);
622 | mBitmap = bm;
623 | setup();
624 | }
625 |
626 | @Override
627 | public void setImageDrawable(Drawable drawable) {
628 | super.setImageDrawable(drawable);
629 | mBitmap = getBitmapFromDrawable(drawable);
630 | setup();
631 | }
632 |
633 | @Override
634 | public void setImageResource(@DrawableRes int resId) {
635 | super.setImageResource(resId);
636 | mBitmap = getBitmapFromDrawable(getDrawable());
637 | setup();
638 | }
639 |
640 | @Override
641 | public void setImageURI(Uri uri) {
642 | super.setImageURI(uri);
643 | mBitmap = uri != null ? getBitmapFromDrawable(getDrawable()) : null;
644 | setup();
645 | }
646 |
647 | @Override
648 | public void setColorFilter(ColorFilter cf) {
649 | if (cf == mColorFilter) return;
650 |
651 | mColorFilter = cf;
652 | mBitmapPaint.setColorFilter(mColorFilter);
653 | mFillPaint.setColorFilter(mColorFilter);
654 | invalidate();
655 | }
656 |
657 | @Override
658 | public void setPadding(int left, int top, int right, int bottom) {
659 | super.setPadding(left, top, right, bottom);
660 | setup();
661 | }
662 |
663 | public String getText() {
664 | return text;
665 | }
666 |
667 | public void setText(String text) {
668 | this.text = text;
669 | setup();
670 | }
671 |
672 | public void setTextResource(@StringRes int textRes) {
673 | setText(getContext().getResources().getString(textRes));
674 | }
675 |
676 | /** @return 返回文字的大小,单位像素 */
677 | public int getTextSize() {
678 | return textSize;
679 | }
680 |
681 | /** @param textSize 设置文字的大小,单位 sp */
682 | public void setTextSize(int textSize) {
683 | this.textSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, textSize, getResources().getDisplayMetrics());
684 | setup();
685 | }
686 |
687 | public int getTextColor() {
688 | return textColor;
689 | }
690 |
691 | public void setTextColor(@ColorInt int textColor) {
692 | this.textColor = textColor;
693 | mTextPaint.setColor(textColor);
694 | setup();
695 | }
696 |
697 | public void setTextColorResource(@ColorRes int textColorRes) {
698 | setTextColor(getContext().getResources().getColor(textColorRes));
699 | }
700 |
701 | public int getBorderWidth() {
702 | return borderWidth;
703 | }
704 |
705 | public void setBorderWidth(int borderWidth) {
706 | this.borderWidth = borderWidth;
707 | setup();
708 | }
709 |
710 | public int getBorderColor() {
711 | return borderColor;
712 | }
713 |
714 | public void setBorderColor(@ColorInt int borderColor) {
715 | this.borderColor = borderColor;
716 | mBorderPaint.setColor(borderColor);
717 | invalidate();
718 | }
719 |
720 | public void setBorderColorResource(@ColorRes int borderColorRes) {
721 | setBorderColor(getContext().getResources().getColor(borderColorRes));
722 | }
723 |
724 | public int getFillColor() {
725 | return fillColor;
726 | }
727 |
728 | public void setFillColor(@ColorInt int fillColor) {
729 | this.fillColor = fillColor;
730 | mFillPaint.setColor(fillColor);
731 | invalidate();
732 | }
733 |
734 | public void setFillColorResource(@ColorRes int fillColorRes) {
735 | setFillColor(getContext().getResources().getColor(fillColorRes));
736 | }
737 |
738 | public int getCorner() {
739 | return corner;
740 | }
741 |
742 | public void setCorner(int corner) {
743 | this.corner = corner;
744 | setup();
745 | }
746 |
747 | public int getBreakLineCount() {
748 | return breakLineCount;
749 | }
750 |
751 | public void setBreakLineCount(int breakLineCount) {
752 | this.breakLineCount = breakLineCount;
753 | setup();
754 | }
755 |
756 | public int getMaxLine() {
757 | return maxLine;
758 | }
759 |
760 | public void setMaxLine(int maxLine) {
761 | this.maxLine = maxLine;
762 | setup();
763 | }
764 |
765 | public int getTextSpacing() {
766 | return textSpacing;
767 | }
768 |
769 | public void setTextSpacing(int textSpacing) {
770 | this.textSpacing = textSpacing;
771 | setup();
772 | }
773 |
774 | public int getHexagonOrientation() {
775 | return hexagonOrientation;
776 | }
777 |
778 | public void setHexagonOrientation(int hexagonOrientation) {
779 | this.hexagonOrientation = hexagonOrientation;
780 | setup();
781 | }
782 |
783 | public boolean isBorderOverlay() {
784 | return borderOverlay;
785 | }
786 |
787 | public void setBorderOverlay(boolean borderOverlay) {
788 | this.borderOverlay = borderOverlay;
789 | setup();
790 | }
791 |
792 | /**
793 | * 核心判断,一个点是否在外凸多边形内
794 | * 原理射线法判断,如果一个点在多边形内部,必然与多边形有奇数个交点
795 | * 反之如果在多边形外部,必然有偶数个交点
796 | */
797 | public static class LassoUtils {
798 | private static final LassoUtils instance = new LassoUtils();//饿汉单例模式
799 | private float[] mPolyX, mPolyY; // 多边形各个点坐标
800 | private int mPolySize; // 有几个点
801 |
802 | private LassoUtils() {
803 | }
804 |
805 | public static LassoUtils getInstance() {
806 | return instance;
807 | }
808 |
809 | /**
810 | * 构造 多边形路径
811 | */
812 | public void setLassoList(List pointFs) {
813 | mPolySize = pointFs.size();
814 | mPolyX = new float[mPolySize];
815 | mPolyY = new float[mPolySize];
816 |
817 | for (int i = 0; i < mPolySize; i++) {
818 | mPolyX[i] = pointFs.get(i).x;
819 | mPolyY[i] = pointFs.get(i).y;
820 | }
821 | }
822 |
823 | /**
824 | * 射线法判断点是否在多边形内部
825 | */
826 | public boolean contains(float x, float y) {
827 | boolean result = false;
828 | for (int i = 0, j = mPolySize - 1; i < mPolySize; j = i++) {
829 | if ((mPolyY[i] < y && mPolyY[j] >= y) || (mPolyY[j] < y && mPolyY[i] >= y)) {
830 | if (mPolyX[i] + (y - mPolyY[i]) / (mPolyY[j] - mPolyY[i]) * (mPolyX[j] - mPolyX[i]) < x) {
831 | result = !result;
832 | }
833 | }
834 | }
835 | return result;
836 | }
837 | }
838 | }
--------------------------------------------------------------------------------
/hexagonview/src/main/res/values/attrs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app', ':hexagonview'
2 |
--------------------------------------------------------------------------------