├── app
├── .gitignore
├── src
│ ├── main
│ │ ├── res
│ │ │ ├── values
│ │ │ │ ├── strings.xml
│ │ │ │ ├── dimens.xml
│ │ │ │ ├── colors.xml
│ │ │ │ └── styles.xml
│ │ │ ├── mipmap-xhdpi
│ │ │ │ ├── ic_add.png
│ │ │ │ ├── ic_bad.png
│ │ │ │ ├── ic_cry.png
│ │ │ │ ├── ic_delete.png
│ │ │ │ ├── ic_error.png
│ │ │ │ ├── ic_good.png
│ │ │ │ ├── ic_right.png
│ │ │ │ ├── ic_smile.png
│ │ │ │ └── ic_launcher.png
│ │ │ ├── mipmap-hdpi
│ │ │ │ └── ic_launcher.png
│ │ │ ├── mipmap-mdpi
│ │ │ │ └── ic_launcher.png
│ │ │ ├── mipmap-xxhdpi
│ │ │ │ └── ic_launcher.png
│ │ │ ├── mipmap-xxxhdpi
│ │ │ │ └── ic_launcher.png
│ │ │ ├── values-w820dp
│ │ │ │ └── dimens.xml
│ │ │ └── layout
│ │ │ │ ├── activity_main.xml
│ │ │ │ ├── activity_test.xml
│ │ │ │ ├── activity_tag_shape.xml
│ │ │ │ ├── activity_tag_reverse.xml
│ │ │ │ ├── activity_tag_choice.xml
│ │ │ │ ├── activity_tag_change.xml
│ │ │ │ ├── activity_tag_edit.xml
│ │ │ │ └── activity_tag_view.xml
│ │ ├── java
│ │ │ └── com
│ │ │ │ └── dl7
│ │ │ │ └── taglayout
│ │ │ │ ├── MyApplication.java
│ │ │ │ ├── TestActivity.java
│ │ │ │ ├── utils
│ │ │ │ ├── TagWordFactory.java
│ │ │ │ ├── RxHelper.java
│ │ │ │ └── ToastUtils.java
│ │ │ │ ├── MainActivity.java
│ │ │ │ ├── TagShapeActivity.java
│ │ │ │ ├── TagReverseActivity.java
│ │ │ │ ├── TagChoiceActivity.java
│ │ │ │ ├── TagEditActivity.java
│ │ │ │ ├── drawable
│ │ │ │ ├── MultiCircleDrawable.java
│ │ │ │ └── CircleDrawable.java
│ │ │ │ ├── TagChangeActivity.java
│ │ │ │ └── TagViewActivity.java
│ │ └── AndroidManifest.xml
│ ├── test
│ │ └── java
│ │ │ └── com
│ │ │ └── dl7
│ │ │ └── taglayout
│ │ │ └── ExampleUnitTest.java
│ └── androidTest
│ │ └── java
│ │ └── com
│ │ └── dl7
│ │ └── taglayout
│ │ └── ApplicationTest.java
├── proguard-rules.pro
└── build.gradle
├── taglayout
├── .gitignore
├── src
│ ├── main
│ │ ├── res
│ │ │ ├── values
│ │ │ │ ├── strings.xml
│ │ │ │ ├── styles.xml
│ │ │ │ └── attrs.xml
│ │ │ └── drawable
│ │ │ │ └── ic_change.png
│ │ ├── AndroidManifest.xml
│ │ └── java
│ │ │ └── com
│ │ │ └── dl7
│ │ │ └── tag
│ │ │ ├── drawable
│ │ │ ├── SimpleCallBack.java
│ │ │ └── RotateDrawable.java
│ │ │ ├── utils
│ │ │ ├── BitmapUtils.java
│ │ │ ├── ColorsFactory.java
│ │ │ └── MeasureUtils.java
│ │ │ ├── TagEditView.java
│ │ │ ├── TagLayout.java
│ │ │ └── TagView.java
│ ├── test
│ │ └── java
│ │ │ └── com
│ │ │ └── dl7
│ │ │ └── tag
│ │ │ └── ExampleUnitTest.java
│ └── androidTest
│ │ └── java
│ │ └── com
│ │ └── dl7
│ │ └── tag
│ │ └── ExampleInstrumentedTest.java
├── proguard-rules.pro
└── build.gradle
├── settings.gradle
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── .gitignore
├── gradle.properties
├── gradlew.bat
├── gradlew
└── README.md
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/taglayout/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app', ':taglayout'
2 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rukey7/TagLayout/HEAD/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | TagLayout
3 |
4 |
--------------------------------------------------------------------------------
/taglayout/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | TagLibrary
3 |
4 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_add.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rukey7/TagLayout/HEAD/app/src/main/res/mipmap-xhdpi/ic_add.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_bad.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rukey7/TagLayout/HEAD/app/src/main/res/mipmap-xhdpi/ic_bad.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_cry.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rukey7/TagLayout/HEAD/app/src/main/res/mipmap-xhdpi/ic_cry.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_delete.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rukey7/TagLayout/HEAD/app/src/main/res/mipmap-xhdpi/ic_delete.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_error.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rukey7/TagLayout/HEAD/app/src/main/res/mipmap-xhdpi/ic_error.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_good.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rukey7/TagLayout/HEAD/app/src/main/res/mipmap-xhdpi/ic_good.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_right.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rukey7/TagLayout/HEAD/app/src/main/res/mipmap-xhdpi/ic_right.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_smile.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rukey7/TagLayout/HEAD/app/src/main/res/mipmap-xhdpi/ic_smile.png
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea/workspace.xml
5 | /.idea/libraries
6 | .DS_Store
7 | /build
8 | /captures
9 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rukey7/TagLayout/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rukey7/TagLayout/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rukey7/TagLayout/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/taglayout/src/main/res/drawable/ic_change.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rukey7/TagLayout/HEAD/taglayout/src/main/res/drawable/ic_change.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rukey7/TagLayout/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rukey7/TagLayout/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 16dp
4 | 16dp
5 |
6 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Mon Dec 28 10:00:20 PST 2015
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip
7 |
--------------------------------------------------------------------------------
/taglayout/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/app/src/main/res/values-w820dp/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 64dp
6 |
7 |
--------------------------------------------------------------------------------
/app/src/test/java/com/dl7/taglayout/ExampleUnitTest.java:
--------------------------------------------------------------------------------
1 | package com.dl7.taglayout;
2 |
3 | import org.junit.Test;
4 |
5 | import static org.junit.Assert.*;
6 |
7 | /**
8 | * To work on unit tests, switch the Test Artifact in the Build Variants view.
9 | */
10 | public class ExampleUnitTest {
11 | @Test
12 | public void addition_isCorrect() throws Exception {
13 | assertEquals(4, 2 + 2);
14 | }
15 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/dl7/taglayout/MyApplication.java:
--------------------------------------------------------------------------------
1 | package com.dl7.taglayout;
2 |
3 | import android.app.Application;
4 |
5 | import com.dl7.taglayout.utils.ToastUtils;
6 |
7 | /**
8 | * Created by long on 2016/12/9.
9 | */
10 |
11 | public class MyApplication extends Application {
12 |
13 | @Override
14 | public void onCreate() {
15 | super.onCreate();
16 | ToastUtils.init(this);
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/com/dl7/taglayout/ApplicationTest.java:
--------------------------------------------------------------------------------
1 | package com.dl7.taglayout;
2 |
3 | import android.app.Application;
4 | import android.test.ApplicationTestCase;
5 |
6 | /**
7 | * Testing Fundamentals
8 | */
9 | public class ApplicationTest extends ApplicationTestCase {
10 | public ApplicationTest() {
11 | super(Application.class);
12 | }
13 | }
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #3F51B5
4 | #303F9F
5 | #FF4081
6 |
7 | #88F44336
8 | #33F44336
9 | #FF666666
10 | #66333333
11 |
12 |
--------------------------------------------------------------------------------
/taglayout/src/test/java/com/dl7/tag/ExampleUnitTest.java:
--------------------------------------------------------------------------------
1 | package com.dl7.tag;
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 | }
--------------------------------------------------------------------------------
/taglayout/src/main/java/com/dl7/tag/drawable/SimpleCallBack.java:
--------------------------------------------------------------------------------
1 | package com.dl7.tag.drawable;
2 |
3 | import android.graphics.drawable.Drawable;
4 |
5 | /**
6 | * Created by long on 2017/4/21.
7 | */
8 |
9 | public abstract class SimpleCallBack implements Drawable.Callback {
10 |
11 | @Override
12 | public void scheduleDrawable(Drawable drawable, Runnable runnable, long l) {
13 | }
14 |
15 | @Override
16 | public void unscheduleDrawable(Drawable drawable, Runnable runnable) {
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in F:\WorkTools\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 |
--------------------------------------------------------------------------------
/taglayout/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in F:\WorkTools\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/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
20 |
21 |
--------------------------------------------------------------------------------
/taglayout/src/androidTest/java/com/dl7/tag/ExampleInstrumentedTest.java:
--------------------------------------------------------------------------------
1 | package com.dl7.tag;
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.dl7.tag.test", appContext.getPackageName());
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 |
3 | # IDE (e.g. Android Studio) users:
4 | # Gradle settings configured through the IDE *will override*
5 | # any settings specified in this file.
6 |
7 | # For more details on how to configure your build environment visit
8 | # http://www.gradle.org/docs/current/userguide/build_environment.html
9 |
10 | # Specifies the JVM arguments used for the daemon process.
11 | # The setting is particularly useful for tweaking memory settings.
12 | # Default value: -Xmx10248m -XX:MaxPermSize=256m
13 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
14 |
15 | # When configured, Gradle will run in incubating parallel mode.
16 | # This option should only be used with decoupled projects. More details, visit
17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
18 | # org.gradle.parallel=true
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | android {
4 | compileSdkVersion 25
5 | buildToolsVersion "25.0.1"
6 |
7 | defaultConfig {
8 | applicationId "com.dl7.taglayout"
9 | minSdkVersion 14
10 | targetSdkVersion 25
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 | testCompile 'junit:junit:4.12'
25 | compile 'com.android.support:appcompat-v7:25.0.1'
26 | compile project(':taglayout')
27 | compile 'io.reactivex:rxjava:1.2.5'
28 | compile 'io.reactivex:rxandroid:1.2.1'
29 | compile 'com.trello:rxlifecycle:1.0'
30 | compile 'com.trello:rxlifecycle-components:1.0'
31 | }
32 |
--------------------------------------------------------------------------------
/app/src/main/java/com/dl7/taglayout/TestActivity.java:
--------------------------------------------------------------------------------
1 | package com.dl7.taglayout;
2 |
3 | import android.os.Bundle;
4 | import android.support.v7.app.AppCompatActivity;
5 |
6 | import com.dl7.tag.TagView;
7 | import com.dl7.taglayout.drawable.CircleDrawable;
8 | import com.dl7.taglayout.drawable.MultiCircleDrawable;
9 |
10 | public class TestActivity extends AppCompatActivity {
11 |
12 | private TagView mTagView;
13 | private TagView mTagMultiCircle;
14 |
15 | @Override
16 | protected void onCreate(Bundle savedInstanceState) {
17 | super.onCreate(savedInstanceState);
18 | setContentView(R.layout.activity_test);
19 | initView();
20 | }
21 |
22 | private void initView() {
23 | mTagView = (TagView) findViewById(R.id.tag_view);
24 | mTagView.setDecorateIcon(new CircleDrawable());
25 | mTagMultiCircle = (TagView) findViewById(R.id.tag_multi_circle);
26 | mTagMultiCircle.setDecorateIcon(new MultiCircleDrawable());
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/taglayout/src/main/java/com/dl7/tag/utils/BitmapUtils.java:
--------------------------------------------------------------------------------
1 | package com.dl7.tag.utils;
2 |
3 | import android.graphics.Bitmap;
4 | import android.graphics.Matrix;
5 |
6 | /**
7 | * Created by Rukey7 on 2016/12/5.
8 | */
9 |
10 | public final class BitmapUtils {
11 |
12 | private BitmapUtils() {
13 | throw new AssertionError();
14 | }
15 |
16 | /**
17 | * 放大缩小图片
18 | *
19 | * @param bitmap 源Bitmap
20 | * @param w 宽
21 | * @param h 高
22 | * @return 目标Bitmap
23 | */
24 | public static Bitmap zoom(Bitmap bitmap, int w, int h) {
25 | int width = bitmap.getWidth();
26 | int height = bitmap.getHeight();
27 | Matrix matrix = new Matrix();
28 | float scaleWidth = ((float) w / width);
29 | float scaleHeight = ((float) h / height);
30 | matrix.postScale(scaleWidth, scaleHeight);
31 | Bitmap newBitmap = Bitmap.createBitmap(bitmap, 0, 0, width, height,
32 | matrix, true);
33 | return newBitmap;
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/taglayout/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 | apply plugin: 'com.github.dcendents.android-maven'
3 | group='com.github.Rukey7'
4 |
5 | android {
6 | compileSdkVersion 25
7 | buildToolsVersion "25.0.1"
8 |
9 | defaultConfig {
10 | minSdkVersion 14
11 | targetSdkVersion 25
12 | versionCode 106
13 | versionName "1.0.6"
14 |
15 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
16 |
17 | }
18 | buildTypes {
19 | release {
20 | minifyEnabled false
21 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
22 | }
23 | }
24 | }
25 |
26 | dependencies {
27 | compile fileTree(dir: 'libs', include: ['*.jar'])
28 | androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
29 | exclude group: 'com.android.support', module: 'support-annotations'
30 | })
31 | compile 'com.android.support:appcompat-v7:25.0.1'
32 | testCompile 'junit:junit:4.12'
33 | }
34 |
--------------------------------------------------------------------------------
/taglayout/src/main/java/com/dl7/tag/utils/ColorsFactory.java:
--------------------------------------------------------------------------------
1 | package com.dl7.tag.utils;
2 |
3 | import android.support.annotation.ColorInt;
4 |
5 | import java.util.Random;
6 |
7 | /**
8 | * Created by long on 2016/12/5.
9 | * 随机颜色生成器
10 | */
11 | public final class ColorsFactory {
12 |
13 | private static final
14 | @ColorInt
15 | int[] COLORS = new int[]{
16 | 0xe51c23, 0xe91e63, 0x9c27b0, 0x673ab7,
17 | 0x3f51b5, 0x5677fc, 0x03a9f4, 0x00bcd4,
18 | 0x009688, 0x259b24, 0x8bc34a, 0x607d8b,
19 | 0xffc107, 0xff9800, 0xff5722, 0x795548, 0x9e9e9e,
20 | };
21 | private static Random sRandom = new Random();
22 |
23 | private ColorsFactory() {
24 | throw new AssertionError();
25 | }
26 |
27 | @ColorInt
28 | public static int[] provideColor() {
29 | int index = sRandom.nextInt(COLORS.length);
30 | int[] colors = new int[2];
31 | colors[0] = COLORS[index] + 0xff000000;
32 | colors[1] = COLORS[index] + 0x88000000;
33 | return colors;
34 | }
35 |
36 | }
37 |
--------------------------------------------------------------------------------
/app/src/main/java/com/dl7/taglayout/utils/TagWordFactory.java:
--------------------------------------------------------------------------------
1 | package com.dl7.taglayout.utils;
2 |
3 | import java.util.Random;
4 |
5 | /**
6 | * Created by long on 2016/12/9.
7 | */
8 |
9 | public final class TagWordFactory {
10 |
11 | public static final String[] TAG_WORD = new String[] {
12 | "Hello", "Android", "Java", "我是TagView", "Hello World",
13 | "This is a long string, This is a long string, This is a long string",
14 | "这是长字符串,这是长字符串,这是长字符串,这是长字符串", "它在我的机器上可以很好运行T_T",
15 | "Learn not and know not.", "Life is but a span.", "Never say die.", "知识改变命运,英语成就未来"
16 | };
17 |
18 | public static final String[] TAG_WORD_2 = new String[] {
19 | "Success", "Failure", "美女", "影视", "豆瓣Top250",
20 | "Have you given any thought to your future?",
21 | "我猜着了开头,但我猜不中这结局"
22 | };
23 |
24 | private static Random sRandom = new Random();
25 |
26 | private TagWordFactory() {
27 | throw new AssertionError();
28 | }
29 |
30 |
31 | public static String provideTagWord() {
32 | return TAG_WORD[sRandom.nextInt(TAG_WORD.length)];
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/app/src/main/java/com/dl7/taglayout/utils/RxHelper.java:
--------------------------------------------------------------------------------
1 | package com.dl7.taglayout.utils;
2 |
3 | import java.util.concurrent.TimeUnit;
4 |
5 | import rx.Observable;
6 | import rx.android.schedulers.AndroidSchedulers;
7 | import rx.functions.Func1;
8 | import rx.schedulers.Schedulers;
9 |
10 | /**
11 | * Created by Rukey7 on 2017/1/22.
12 | */
13 |
14 | public final class RxHelper {
15 |
16 | public static Observable countdown(int time) {
17 | if (time < 0) {
18 | time = 0;
19 | }
20 | final int countTime = time;
21 |
22 | return Observable.interval(0, 1, TimeUnit.SECONDS)
23 | .map(new Func1() {
24 | @Override
25 | public Integer call(Long increaseTime) {
26 | return countTime - increaseTime.intValue();
27 | }
28 | })
29 | .take(countTime + 1)
30 | .subscribeOn(Schedulers.io())
31 | .unsubscribeOn(Schedulers.io())
32 | .subscribeOn(AndroidSchedulers.mainThread())
33 | .observeOn(AndroidSchedulers.mainThread());
34 |
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/taglayout/src/main/java/com/dl7/tag/utils/MeasureUtils.java:
--------------------------------------------------------------------------------
1 | package com.dl7.tag.utils;
2 |
3 | import android.content.Context;
4 | import android.graphics.Paint;
5 | import android.util.TypedValue;
6 |
7 | /**
8 | * Created by Rukey7 on 2016/7/18.
9 | * 测量帮助类
10 | */
11 | public class MeasureUtils {
12 |
13 | private MeasureUtils() {
14 | throw new AssertionError();
15 | }
16 |
17 |
18 | public static float dp2px(Context context, float dp) {
19 | return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp,
20 | context.getResources().getDisplayMetrics());
21 | }
22 |
23 | public static float sp2px(Context context, float sp) {
24 | return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, sp,
25 | context.getResources().getDisplayMetrics());
26 | }
27 |
28 | public static float px2sp(Context context, float pxValue) {
29 | final float fontScale = context.getResources().getDisplayMetrics().scaledDensity;
30 | return pxValue / fontScale;
31 | }
32 |
33 | /**
34 | * 获取字体高度
35 | * @param fontSize
36 | * @return
37 | */
38 | public static int getFontHeight(float fontSize) {
39 | Paint paint = new Paint();
40 | paint.setTextSize(fontSize);
41 | Paint.FontMetrics fm = paint.getFontMetrics();
42 | return (int) Math.ceil(fm.descent - fm.top) + 2;
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/app/src/main/java/com/dl7/taglayout/utils/ToastUtils.java:
--------------------------------------------------------------------------------
1 | package com.dl7.taglayout.utils;
2 |
3 | import android.content.Context;
4 | import android.widget.Toast;
5 |
6 | /**
7 | * Created by long on 2016/6/6.
8 | * 避免同样的信息多次触发重复弹出的问题
9 | */
10 | public class ToastUtils {
11 |
12 | private static Context sContext;
13 | private static String oldMsg;
14 | protected static Toast toast = null;
15 | private static long oneTime = 0;
16 | private static long twoTime = 0;
17 |
18 | private ToastUtils() {
19 | throw new RuntimeException("ToastUtils cannot be initialized!");
20 | }
21 |
22 | public static void init(Context context) {
23 | sContext = context;
24 | }
25 |
26 | public static void showToast(String s) {
27 | if (toast == null) {
28 | toast = Toast.makeText(sContext, s, Toast.LENGTH_SHORT);
29 | toast.show();
30 | oneTime = System.currentTimeMillis();
31 | } else {
32 | twoTime = System.currentTimeMillis();
33 | if (s.equals(oldMsg)) {
34 | if (twoTime - oneTime > Toast.LENGTH_SHORT) {
35 | toast.show();
36 | }
37 | } else {
38 | oldMsg = s;
39 | toast.setText(s);
40 | toast.show();
41 | }
42 | oneTime = twoTime;
43 | }
44 | }
45 |
46 | public static void showToast(int resId) {
47 | showToast(sContext.getString(resId));
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/app/src/main/java/com/dl7/taglayout/MainActivity.java:
--------------------------------------------------------------------------------
1 | package com.dl7.taglayout;
2 |
3 | import android.content.Intent;
4 | import android.os.Bundle;
5 | import android.support.v7.app.AppCompatActivity;
6 |
7 | import com.dl7.tag.TagLayout;
8 | import com.dl7.tag.TagView;
9 |
10 | public class MainActivity extends AppCompatActivity {
11 |
12 | private final String[] mTagWords = new String[]{
13 | "不同边框形状的标签", "单选和多选标签", "可编辑的标签", "动画效果的换一换标签",
14 | "TagView的一些其它用途", "水平反向排列(RTL)"
15 | };
16 | private TagLayout mTagLayout;
17 |
18 | @Override
19 | protected void onCreate(Bundle savedInstanceState) {
20 | super.onCreate(savedInstanceState);
21 | setContentView(R.layout.activity_main);
22 | initView();
23 | }
24 |
25 | private void initView() {
26 | mTagLayout = (TagLayout) findViewById(R.id.tag_layout);
27 | mTagLayout.setTags(mTagWords);
28 | mTagLayout.setTagClickListener(new TagView.OnTagClickListener() {
29 | @Override
30 | public void onTagClick(int position, String text, @TagView.TagMode int tagMode) {
31 | if (mTagWords[0].equals(text)) {
32 | startActivity(new Intent(MainActivity.this, TagShapeActivity.class));
33 | } else if (mTagWords[1].equals(text)) {
34 | startActivity(new Intent(MainActivity.this, TagChoiceActivity.class));
35 | } else if (mTagWords[2].equals(text)) {
36 | startActivity(new Intent(MainActivity.this, TagEditActivity.class));
37 | } else if (mTagWords[3].equals(text)) {
38 | startActivity(new Intent(MainActivity.this, TagChangeActivity.class));
39 | } else if (mTagWords[4].equals(text)) {
40 | startActivity(new Intent(MainActivity.this, TagViewActivity.class));
41 | } else if (mTagWords[5].equals(text)) {
42 | startActivity(new Intent(MainActivity.this, TagReverseActivity.class));
43 | }
44 | }
45 | });
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_test.xml:
--------------------------------------------------------------------------------
1 |
2 |
13 |
14 |
31 |
32 |
49 |
50 |
--------------------------------------------------------------------------------
/taglayout/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
20 |
21 |
24 |
25 |
28 |
29 |
32 |
33 |
36 |
37 |
40 |
41 |
45 |
46 |
49 |
50 |
53 |
54 |
57 |
58 |
61 |
62 |
65 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/app/src/main/java/com/dl7/taglayout/TagShapeActivity.java:
--------------------------------------------------------------------------------
1 | package com.dl7.taglayout;
2 |
3 | import android.os.Bundle;
4 | import android.support.v7.app.AppCompatActivity;
5 |
6 | import com.dl7.tag.TagLayout;
7 | import com.dl7.tag.TagView;
8 | import com.dl7.taglayout.utils.TagWordFactory;
9 | import com.dl7.taglayout.utils.ToastUtils;
10 |
11 | public class TagShapeActivity extends AppCompatActivity implements TagView.OnTagClickListener, TagView.OnTagLongClickListener {
12 |
13 | private TagLayout mTagLayout1;
14 | private TagLayout mTagLayout2;
15 | private TagLayout mTagLayout3;
16 | private TagView mTagDel;
17 | private TagView mTagAdd;
18 |
19 | @Override
20 | protected void onCreate(Bundle savedInstanceState) {
21 | super.onCreate(savedInstanceState);
22 | setContentView(R.layout.activity_tag_shape);
23 | initView();
24 | }
25 |
26 | private void initView() {
27 | mTagLayout1 = (TagLayout) findViewById(R.id.tag_layout_1);
28 | mTagLayout2 = (TagLayout) findViewById(R.id.tag_layout_2);
29 | mTagLayout3 = (TagLayout) findViewById(R.id.tag_layout_3);
30 | mTagDel = (TagView) findViewById(R.id.tag_del);
31 | mTagAdd = (TagView) findViewById(R.id.tag_add);
32 | mTagLayout1.setTagClickListener(this);
33 | mTagLayout1.setTagLongClickListener(this);
34 | mTagLayout2.setTagClickListener(this);
35 | mTagLayout2.setTagLongClickListener(this);
36 | mTagLayout3.setTagClickListener(this);
37 | mTagLayout3.setTagLongClickListener(this);
38 |
39 | mTagAdd.setTagClickListener(new TagView.OnTagClickListener() {
40 | @Override
41 | public void onTagClick(int position, String text, @TagView.TagMode int tagMode) {
42 | String word = TagWordFactory.provideTagWord();
43 | mTagLayout1.addTag(word);
44 | mTagLayout2.addTag(word);
45 | mTagLayout3.addTag(word);
46 | }
47 | });
48 | mTagDel.setTagClickListener(new TagView.OnTagClickListener() {
49 | @Override
50 | public void onTagClick(int position, String text, @TagView.TagMode int tagMode) {
51 | mTagLayout1.deleteTag(0);
52 | mTagLayout2.deleteTag(0);
53 | mTagLayout3.deleteTag(0);
54 | }
55 | });
56 | }
57 |
58 | @Override
59 | public void onTagClick(int position, String text, @TagView.TagMode int tagMode) {
60 | ToastUtils.showToast(text);
61 | }
62 |
63 | @Override
64 | public void onTagLongClick(int position, String text, @TagView.TagMode int tagMode) {
65 | ToastUtils.showToast("长按:" + text);
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/app/src/main/java/com/dl7/taglayout/TagReverseActivity.java:
--------------------------------------------------------------------------------
1 | package com.dl7.taglayout;
2 |
3 | import android.os.Bundle;
4 | import android.support.v7.app.AppCompatActivity;
5 |
6 | import com.dl7.tag.TagLayout;
7 | import com.dl7.tag.TagView;
8 | import com.dl7.taglayout.utils.TagWordFactory;
9 | import com.dl7.taglayout.utils.ToastUtils;
10 |
11 | public class TagReverseActivity extends AppCompatActivity implements TagView.OnTagClickListener, TagView.OnTagLongClickListener {
12 |
13 | private TagLayout mTagLayout1;
14 | private TagLayout mTagLayout2;
15 | private TagLayout mTagLayout3;
16 | private TagView mTagDel;
17 | private TagView mTagAdd;
18 |
19 | @Override
20 | protected void onCreate(Bundle savedInstanceState) {
21 | super.onCreate(savedInstanceState);
22 | setContentView(R.layout.activity_tag_reverse);
23 | initView();
24 | }
25 |
26 | private void initView() {
27 | mTagLayout1 = (TagLayout) findViewById(R.id.tag_layout_1);
28 | mTagLayout2 = (TagLayout) findViewById(R.id.tag_layout_2);
29 | mTagLayout3 = (TagLayout) findViewById(R.id.tag_layout_3);
30 | mTagDel = (TagView) findViewById(R.id.tag_del);
31 | mTagAdd = (TagView) findViewById(R.id.tag_add);
32 | mTagLayout1.setTagClickListener(this);
33 | mTagLayout1.setTagLongClickListener(this);
34 | mTagLayout2.setTagClickListener(this);
35 | mTagLayout2.setTagLongClickListener(this);
36 | mTagLayout3.setTagClickListener(this);
37 | mTagLayout3.setTagLongClickListener(this);
38 |
39 | mTagAdd.setTagClickListener(new TagView.OnTagClickListener() {
40 | @Override
41 | public void onTagClick(int position, String text, @TagView.TagMode int tagMode) {
42 | String word = TagWordFactory.provideTagWord();
43 | mTagLayout1.addTag(word);
44 | mTagLayout2.addTag(word);
45 | mTagLayout3.addTag(word);
46 | }
47 | });
48 | mTagDel.setTagClickListener(new TagView.OnTagClickListener() {
49 | @Override
50 | public void onTagClick(int position, String text, @TagView.TagMode int tagMode) {
51 | mTagLayout1.deleteTag(0);
52 | mTagLayout2.deleteTag(0);
53 | mTagLayout3.deleteTag(0);
54 | }
55 | });
56 | }
57 |
58 | @Override
59 | public void onTagClick(int position, String text, @TagView.TagMode int tagMode) {
60 | ToastUtils.showToast(text);
61 | }
62 |
63 | @Override
64 | public void onTagLongClick(int position, String text, @TagView.TagMode int tagMode) {
65 | ToastUtils.showToast("长按:" + text);
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/app/src/main/java/com/dl7/taglayout/TagChoiceActivity.java:
--------------------------------------------------------------------------------
1 | package com.dl7.taglayout;
2 |
3 | import android.os.Bundle;
4 | import android.support.v7.app.AppCompatActivity;
5 |
6 | import com.dl7.tag.TagLayout;
7 | import com.dl7.tag.TagView;
8 | import com.dl7.taglayout.utils.TagWordFactory;
9 | import com.dl7.taglayout.utils.ToastUtils;
10 |
11 | public class TagChoiceActivity extends AppCompatActivity implements TagView.OnTagClickListener, TagView.OnTagLongClickListener {
12 |
13 | private TagLayout mTagLayout1;
14 | private TagLayout mTagLayout2;
15 | private TagLayout mTagLayout3;
16 | private TagView mTagDel;
17 | private TagView mTagAdd;
18 |
19 | @Override
20 | protected void onCreate(Bundle savedInstanceState) {
21 | super.onCreate(savedInstanceState);
22 | setContentView(R.layout.activity_tag_choice);
23 | initView();
24 | }
25 |
26 | private void initView() {
27 | mTagLayout1 = (TagLayout) findViewById(R.id.tag_layout_1);
28 | mTagLayout2 = (TagLayout) findViewById(R.id.tag_layout_2);
29 | mTagLayout3 = (TagLayout) findViewById(R.id.tag_layout_3);
30 | mTagDel = (TagView) findViewById(R.id.tag_del);
31 | mTagAdd = (TagView) findViewById(R.id.tag_add);
32 | mTagLayout1.setTagClickListener(this);
33 | mTagLayout1.setTagLongClickListener(this);
34 | mTagLayout2.setTagClickListener(this);
35 | mTagLayout2.setTagLongClickListener(this);
36 | mTagLayout3.setTagClickListener(this);
37 | mTagLayout3.setTagLongClickListener(this);
38 |
39 | mTagAdd.setTagClickListener(new TagView.OnTagClickListener() {
40 | @Override
41 | public void onTagClick(int position, String text, @TagView.TagMode int tagMode) {
42 | String word = TagWordFactory.provideTagWord();
43 | mTagLayout1.addTag(word);
44 | mTagLayout2.addTag(word);
45 | mTagLayout3.addTag(word);
46 | }
47 | });
48 | mTagDel.setTagClickListener(new TagView.OnTagClickListener() {
49 | @Override
50 | public void onTagClick(int position, String text, @TagView.TagMode int tagMode) {
51 | mTagLayout1.deleteCheckedTags();
52 | mTagLayout2.deleteCheckedTags();
53 | mTagLayout3.deleteCheckedTags();
54 | }
55 | });
56 | }
57 |
58 | @Override
59 | public void onTagClick(int position, String text, @TagView.TagMode int tagMode) {
60 | ToastUtils.showToast(text);
61 | }
62 |
63 | @Override
64 | public void onTagLongClick(int position, String text, @TagView.TagMode int tagMode) {
65 | ToastUtils.showToast("长按:" + text);
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/app/src/main/java/com/dl7/taglayout/TagEditActivity.java:
--------------------------------------------------------------------------------
1 | package com.dl7.taglayout;
2 |
3 | import android.os.Bundle;
4 | import android.support.v7.app.AppCompatActivity;
5 |
6 | import com.dl7.tag.TagLayout;
7 | import com.dl7.tag.TagView;
8 | import com.dl7.taglayout.utils.TagWordFactory;
9 | import com.dl7.taglayout.utils.ToastUtils;
10 |
11 | public class TagEditActivity extends AppCompatActivity implements TagView.OnTagClickListener, TagView.OnTagLongClickListener {
12 |
13 | private TagLayout mTagLayout1;
14 | private TagLayout mTagLayout2;
15 | private TagLayout mTagLayout3;
16 | private TagView mTagDel;
17 | private TagView mTagAdd;
18 | private TagView mTagEditControl;
19 |
20 | @Override
21 | protected void onCreate(Bundle savedInstanceState) {
22 | super.onCreate(savedInstanceState);
23 | setContentView(R.layout.activity_tag_edit);
24 | initView();
25 | }
26 |
27 | private void initView() {
28 | mTagLayout1 = (TagLayout) findViewById(R.id.tag_layout_1);
29 | mTagLayout2 = (TagLayout) findViewById(R.id.tag_layout_2);
30 | mTagLayout3 = (TagLayout) findViewById(R.id.tag_layout_3);
31 | mTagDel = (TagView) findViewById(R.id.tag_del);
32 | mTagAdd = (TagView) findViewById(R.id.tag_add);
33 | mTagEditControl = (TagView) findViewById(R.id.tag_open_edit);
34 | mTagLayout1.setTagClickListener(this);
35 | mTagLayout1.setTagLongClickListener(this);
36 | mTagLayout2.setTagClickListener(this);
37 | mTagLayout2.setTagLongClickListener(this);
38 | mTagLayout3.setTagClickListener(this);
39 | mTagLayout3.setTagLongClickListener(this);
40 |
41 | mTagAdd.setTagClickListener(new TagView.OnTagClickListener() {
42 | @Override
43 | public void onTagClick(int position, String text, @TagView.TagMode int tagMode) {
44 | String word = TagWordFactory.provideTagWord();
45 | mTagLayout1.addTag(word);
46 | mTagLayout2.addTag(word);
47 | mTagLayout3.addTag(word);
48 | }
49 | });
50 | mTagDel.setTagClickListener(new TagView.OnTagClickListener() {
51 | @Override
52 | public void onTagClick(int position, String text, @TagView.TagMode int tagMode) {
53 | mTagLayout1.deleteTag(0);
54 | mTagLayout2.deleteTag(0);
55 | mTagLayout3.deleteTag(0);
56 | }
57 | });
58 | mTagEditControl.setTagCheckListener(new TagView.OnTagCheckListener() {
59 | @Override
60 | public void onTagCheck(int position, String text, boolean isChecked) {
61 | if (isChecked) {
62 | mTagLayout1.entryEditMode();
63 | mTagLayout2.entryEditMode();
64 | mTagLayout3.entryEditMode();
65 | } else {
66 | mTagLayout1.exitEditMode();
67 | mTagLayout2.exitEditMode();
68 | mTagLayout3.exitEditMode();
69 | }
70 | }
71 | });
72 | }
73 |
74 | @Override
75 | public void onTagClick(int position, String text, @TagView.TagMode int tagMode) {
76 | ToastUtils.showToast(text);
77 | }
78 |
79 | @Override
80 | public void onTagLongClick(int position, String text, @TagView.TagMode int tagMode) {
81 | ToastUtils.showToast("长按:" + text);
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/app/src/main/java/com/dl7/taglayout/drawable/MultiCircleDrawable.java:
--------------------------------------------------------------------------------
1 | package com.dl7.taglayout.drawable;
2 |
3 | import android.graphics.Canvas;
4 | import android.graphics.ColorFilter;
5 | import android.graphics.PixelFormat;
6 | import android.graphics.Rect;
7 | import android.graphics.drawable.Animatable;
8 | import android.graphics.drawable.Drawable;
9 |
10 | /**
11 | * Created by long on 2016/7/2.
12 | * 复数Circle的Drawable,需要实现Drawable.Callback接口
13 | */
14 | public class MultiCircleDrawable extends Drawable implements Animatable, Drawable.Callback {
15 |
16 | // 每个Drawable动画启动的间隔
17 | private static final int EACH_CIRCLE_SPACE = 200;
18 | // CircleDrawable数组
19 | private CircleDrawable[] mCircleDrawables;
20 |
21 |
22 | public MultiCircleDrawable() {
23 | mCircleDrawables = new CircleDrawable[] {
24 | new CircleDrawable(),
25 | new CircleDrawable(),
26 | new CircleDrawable()
27 | };
28 | for (int i = 0; i < mCircleDrawables.length; i++) {
29 | // 设置动画启动延迟
30 | mCircleDrawables[i].setAnimatorDelay(EACH_CIRCLE_SPACE * i);
31 | // 设置回调监听,当CircleDrawable发生重绘时就会调用 invalidateDrawable(Drawable who) 方法
32 | mCircleDrawables[i].setCallback(this);
33 | }
34 | }
35 |
36 |
37 | @Override
38 | public void draw(Canvas canvas) {
39 | for (CircleDrawable drawable : mCircleDrawables) {
40 | // 分层绘制每个CircleDrawable
41 | int count = canvas.save();
42 | drawable.draw(canvas);
43 | canvas.restoreToCount(count);
44 | }
45 | }
46 |
47 | @Override
48 | public void setAlpha(int alpha) {
49 | }
50 |
51 | @Override
52 | public void setColorFilter(ColorFilter colorFilter) {
53 | for (CircleDrawable drawable : mCircleDrawables) {
54 | drawable.setColorFilter(colorFilter);
55 | }
56 | }
57 |
58 | @Override
59 | public int getOpacity() {
60 | return PixelFormat.TRANSLUCENT;
61 | }
62 |
63 | @Override
64 | protected void onBoundsChange(Rect bounds) {
65 | super.onBoundsChange(bounds);
66 | for (CircleDrawable drawable : mCircleDrawables) {
67 | drawable.onBoundsChange(bounds);
68 | }
69 | }
70 | /************************************************************/
71 |
72 | @Override
73 | public void start() {
74 | for (CircleDrawable drawable : mCircleDrawables) {
75 | drawable.start();
76 | }
77 | }
78 |
79 | @Override
80 | public void stop() {
81 | for (CircleDrawable drawable : mCircleDrawables) {
82 | drawable.stop();
83 | }
84 | }
85 |
86 | @Override
87 | public boolean isRunning() {
88 | for (CircleDrawable drawable : mCircleDrawables) {
89 | if (drawable.isRunning()) {
90 | return true;
91 | }
92 | }
93 | return false;
94 | }
95 |
96 | @Override
97 | public void invalidateDrawable(Drawable who) {
98 | // 需要重绘,子Drawable发生重绘会调用这个方法通知父Drawable,如果有设置Callback回调监听的话
99 | invalidateSelf();
100 | }
101 |
102 | @Override
103 | public void scheduleDrawable(Drawable who, Runnable what, long when) {
104 | }
105 |
106 | @Override
107 | public void unscheduleDrawable(Drawable who, Runnable what) {
108 | }
109 | }
110 |
--------------------------------------------------------------------------------
/taglayout/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 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
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 |
--------------------------------------------------------------------------------
/taglayout/src/main/java/com/dl7/tag/drawable/RotateDrawable.java:
--------------------------------------------------------------------------------
1 | package com.dl7.tag.drawable;
2 |
3 | import android.animation.ValueAnimator;
4 | import android.graphics.Bitmap;
5 | import android.graphics.BitmapShader;
6 | import android.graphics.Canvas;
7 | import android.graphics.ColorFilter;
8 | import android.graphics.Paint;
9 | import android.graphics.PixelFormat;
10 | import android.graphics.Rect;
11 | import android.graphics.RectF;
12 | import android.graphics.Shader;
13 | import android.graphics.drawable.Animatable;
14 | import android.graphics.drawable.Drawable;
15 | import android.view.animation.AccelerateDecelerateInterpolator;
16 |
17 | import com.dl7.tag.utils.BitmapUtils;
18 |
19 | /**
20 | * Created by Rukey7 on 2016/12/5.
21 | * 旋转动画 Drawable
22 | */
23 | public class RotateDrawable extends Drawable implements Animatable {
24 |
25 | private Paint mPaint;
26 | // 绘制的矩形框
27 | private RectF mRect = new RectF();
28 | // 动画控制
29 | private ValueAnimator mValueAnimator;
30 | // 旋转角度
31 | private float mRotate;
32 | // icon
33 | private Bitmap mBitmap;
34 | // 偏移
35 | private int mTranslationX;
36 | private int mTranslationY;
37 |
38 |
39 | public RotateDrawable(Bitmap bitmap) {
40 | mBitmap = bitmap;
41 | mPaint = new Paint();
42 | mPaint.setAntiAlias(true);
43 | }
44 |
45 | @Override
46 | public void draw(Canvas canvas) {
47 | canvas.translate(mTranslationX, mTranslationY);
48 | canvas.rotate(mRotate, mRect.width() / 2, mRect.height() / 2);
49 | canvas.drawPaint(mPaint);
50 | }
51 |
52 | @Override
53 | public void setAlpha(int alpha) {
54 | mPaint.setAlpha(alpha);
55 | }
56 |
57 | @Override
58 | public void setColorFilter(ColorFilter colorFilter) {
59 | mPaint.setColorFilter(colorFilter);
60 | }
61 |
62 | @Override
63 | public int getOpacity() {
64 | return PixelFormat.TRANSLUCENT;
65 | }
66 |
67 | @Override
68 | protected void onBoundsChange(Rect bounds) {
69 | super.onBoundsChange(bounds);
70 | mRect.set(_clipSquare(bounds));
71 | mTranslationX = (int) mRect.left;
72 | mTranslationY = (int) mRect.top;
73 | // 缩放 Bitmap
74 | Bitmap zoom = BitmapUtils.zoom(mBitmap, bounds.width(), bounds.height());
75 | BitmapShader bitmapShader = new BitmapShader(zoom, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
76 | mPaint.setShader(bitmapShader);
77 | if (isRunning()) {
78 | stop();
79 | }
80 | // 设置动画
81 | mValueAnimator = ValueAnimator.ofFloat(0, 2880).setDuration(2000);
82 | mValueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
83 | boolean isOver = false;
84 |
85 | @Override
86 | public void onAnimationUpdate(ValueAnimator animation) {
87 | mRotate = (float) animation.getAnimatedValue();
88 | if (mRotate <= 2160) {
89 | isOver = false;
90 | } else if (!isOver) {
91 | isOver = true;
92 | mRotate = 2160;
93 | }
94 | invalidateSelf();
95 | }
96 | });
97 | // 设置动画无限循环
98 | mValueAnimator.setInterpolator(new AccelerateDecelerateInterpolator());
99 | mValueAnimator.setRepeatMode(ValueAnimator.RESTART);
100 | mValueAnimator.setRepeatCount(ValueAnimator.INFINITE);
101 | start();
102 | }
103 |
104 | /**
105 | * 裁剪Rect为正方形
106 | * @param rect
107 | * @return
108 | */
109 | private Rect _clipSquare(Rect rect) {
110 | int w = rect.width();
111 | int h = rect.height();
112 | int min = Math.min(w, h);
113 | int cx = rect.centerX();
114 | int cy = rect.centerY();
115 | int r = min / 2;
116 | return new Rect(
117 | cx - r,
118 | cy - r,
119 | cx + r,
120 | cy + r
121 | );
122 | }
123 |
124 | /**
125 | * ==================================== 显示模式 ====================================
126 | */
127 |
128 | @Override
129 | public void start() {
130 | mValueAnimator.start();
131 | }
132 |
133 | @Override
134 | public void stop() {
135 | mValueAnimator.end();
136 | }
137 |
138 | @Override
139 | public boolean isRunning() {
140 | return mValueAnimator != null && mValueAnimator.isRunning();
141 | }
142 |
143 | }
144 |
--------------------------------------------------------------------------------
/app/src/main/java/com/dl7/taglayout/drawable/CircleDrawable.java:
--------------------------------------------------------------------------------
1 | package com.dl7.taglayout.drawable;
2 |
3 | import android.animation.ObjectAnimator;
4 | import android.animation.PropertyValuesHolder;
5 | import android.animation.ValueAnimator;
6 | import android.graphics.Canvas;
7 | import android.graphics.Color;
8 | import android.graphics.ColorFilter;
9 | import android.graphics.Paint;
10 | import android.graphics.PixelFormat;
11 | import android.graphics.Rect;
12 | import android.graphics.RectF;
13 | import android.graphics.drawable.Animatable;
14 | import android.graphics.drawable.Drawable;
15 | import android.util.Property;
16 |
17 | /**
18 | * Created by long on 2016/7/2.
19 | * 圆圈Drawable
20 | */
21 | public class CircleDrawable extends Drawable implements Animatable {
22 |
23 | private Paint mPaint;
24 | // 动画控制
25 | private ValueAnimator mValueAnimator;
26 | // 扩散半径
27 | private int mRadius;
28 | // 绘制的矩形框
29 | private RectF mRect = new RectF();
30 | // 动画启动延迟时间
31 | private int mStartDelay;
32 |
33 | // 自定义一个扩散半径属性
34 | Property mRadiusProperty = new Property(Integer.class, "radius") {
35 | @Override
36 | public void set(CircleDrawable object, Integer value) {
37 | object.setRadius(value);
38 | }
39 |
40 | @Override
41 | public Integer get(CircleDrawable object) {
42 | return object.getRadius();
43 | }
44 | };
45 | public int getRadius() {
46 | return mRadius;
47 | }
48 | public void setRadius(int radius) {
49 | mRadius = radius;
50 | }
51 |
52 |
53 | public CircleDrawable() {
54 | mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
55 | mPaint.setColor(Color.WHITE);
56 | mPaint.setStyle(Paint.Style.STROKE);
57 | mPaint.setStrokeWidth(2);
58 | }
59 |
60 |
61 | @Override
62 | public void draw(Canvas canvas) {
63 | // 绘制圆圈
64 | canvas.drawCircle(mRect.centerX(), mRect.centerY(), mRadius, mPaint);
65 | }
66 |
67 | @Override
68 | public void setAlpha(int alpha) {
69 | mPaint.setAlpha(alpha);
70 | }
71 |
72 | @Override
73 | public void setColorFilter(ColorFilter colorFilter) {
74 | mPaint.setColorFilter(colorFilter);
75 | }
76 |
77 | @Override
78 | public int getOpacity() {
79 | return PixelFormat.TRANSLUCENT;
80 | }
81 |
82 | @Override
83 | protected void onBoundsChange(Rect bounds) {
84 | super.onBoundsChange(bounds);
85 | mRect.set(_clipSquare(bounds));
86 | if (isRunning()) {
87 | stop();
88 | }
89 | // 计算最大半径
90 | int maxRadius = (int) ((mRect.right - mRect.left) / 2);
91 | // 控制扩散半径的属性变化
92 | PropertyValuesHolder radiusHolder = PropertyValuesHolder.ofInt(mRadiusProperty, 0, maxRadius);
93 | // 控制透明度的属性变化
94 | PropertyValuesHolder alphaHolder = PropertyValuesHolder.ofInt("alpha", 255, 0);
95 | mValueAnimator = ObjectAnimator.ofPropertyValuesHolder(this, radiusHolder, alphaHolder);
96 | mValueAnimator.setStartDelay(mStartDelay);
97 | mValueAnimator.setDuration(1200);
98 | mValueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
99 | @Override
100 | public void onAnimationUpdate(ValueAnimator animation) {
101 | // 监听属性动画并进行重绘
102 | invalidateSelf();
103 | }
104 | });
105 | // 设置动画无限循环
106 | mValueAnimator.setRepeatMode(ValueAnimator.RESTART);
107 | mValueAnimator.setRepeatCount(ValueAnimator.INFINITE);
108 | start();
109 | }
110 |
111 | /**
112 | * 裁剪Rect为正方形
113 | * @param rect
114 | * @return
115 | */
116 | private Rect _clipSquare(Rect rect) {
117 | int w = rect.width();
118 | int h = rect.height();
119 | int min = Math.min(w, h);
120 | int cx = rect.centerX();
121 | int cy = rect.centerY();
122 | int r = min / 2;
123 | return new Rect(
124 | cx - r,
125 | cy - r,
126 | cx + r,
127 | cy + r
128 | );
129 | }
130 |
131 | /************************************************************/
132 |
133 | @Override
134 | public void start() {
135 | mValueAnimator.start();
136 | }
137 |
138 | @Override
139 | public void stop() {
140 | mValueAnimator.end();
141 | }
142 |
143 | @Override
144 | public boolean isRunning() {
145 | return mValueAnimator != null && mValueAnimator.isRunning();
146 | }
147 |
148 | public void setAnimatorDelay(int startDelay) {
149 | mStartDelay = startDelay;
150 | }
151 | }
152 |
--------------------------------------------------------------------------------
/app/src/main/java/com/dl7/taglayout/TagChangeActivity.java:
--------------------------------------------------------------------------------
1 | package com.dl7.taglayout;
2 |
3 | import android.os.Bundle;
4 | import android.support.v7.app.AppCompatActivity;
5 |
6 | import com.dl7.tag.TagLayout;
7 | import com.dl7.tag.TagView;
8 | import com.dl7.taglayout.utils.TagWordFactory;
9 | import com.dl7.taglayout.utils.ToastUtils;
10 |
11 | public class TagChangeActivity extends AppCompatActivity implements TagView.OnTagLongClickListener {
12 |
13 | private TagLayout mTagLayout1;
14 | private TagLayout mTagLayout2;
15 | private TagLayout mTagLayout3;
16 | private TagView mTagDel;
17 | private TagView mTagAdd;
18 |
19 | @Override
20 | protected void onCreate(Bundle savedInstanceState) {
21 | super.onCreate(savedInstanceState);
22 | setContentView(R.layout.activity_tag_change);
23 | initView();
24 | }
25 |
26 | private void initView() {
27 | mTagLayout1 = (TagLayout) findViewById(R.id.tag_layout_1);
28 | mTagLayout2 = (TagLayout) findViewById(R.id.tag_layout_2);
29 | mTagLayout3 = (TagLayout) findViewById(R.id.tag_layout_3);
30 | mTagDel = (TagView) findViewById(R.id.tag_del);
31 | mTagAdd = (TagView) findViewById(R.id.tag_add);
32 | mTagLayout1.setTagClickListener(new TagView.OnTagClickListener() {
33 | boolean isChange = false;
34 |
35 | @Override
36 | public void onTagClick(int position, String text, @TagView.TagMode int tagMode) {
37 | if (tagMode == TagView.MODE_CHANGE) {
38 | if (isChange) {
39 | mTagLayout1.updateTags(TagWordFactory.TAG_WORD);
40 | } else {
41 | mTagLayout1.updateTags(TagWordFactory.TAG_WORD_2);
42 | }
43 | isChange = !isChange;
44 | } else {
45 | ToastUtils.showToast(text);
46 | }
47 | }
48 | });
49 | mTagLayout2.setTagClickListener(new TagView.OnTagClickListener() {
50 | boolean isChange = false;
51 |
52 | @Override
53 | public void onTagClick(int position, String text, @TagView.TagMode int tagMode) {
54 | if (tagMode == TagView.MODE_CHANGE) {
55 | if (isChange) {
56 | mTagLayout2.updateTags(TagWordFactory.TAG_WORD);
57 | } else {
58 | mTagLayout2.updateTags(TagWordFactory.TAG_WORD_2);
59 | }
60 | isChange = !isChange;
61 | } else {
62 | ToastUtils.showToast(text);
63 | }
64 | }
65 | });
66 | mTagLayout3.setTagClickListener(new TagView.OnTagClickListener() {
67 | boolean isChange = false;
68 |
69 | @Override
70 | public void onTagClick(int position, String text, @TagView.TagMode int tagMode) {
71 | if (tagMode == TagView.MODE_CHANGE) {
72 | if (isChange) {
73 | mTagLayout3.updateTags(TagWordFactory.TAG_WORD);
74 | } else {
75 | mTagLayout3.updateTags(TagWordFactory.TAG_WORD_2);
76 | }
77 | isChange = !isChange;
78 | } else {
79 | ToastUtils.showToast(text);
80 | }
81 | }
82 | });
83 | mTagLayout1.setTagLongClickListener(this);
84 | mTagLayout2.setTagLongClickListener(this);
85 | mTagLayout3.setTagLongClickListener(this);
86 |
87 | mTagAdd.setTagClickListener(new TagView.OnTagClickListener() {
88 | @Override
89 | public void onTagClick(int position, String text, @TagView.TagMode int tagMode) {
90 | String word = TagWordFactory.provideTagWord();
91 | mTagLayout1.addTag(word);
92 | mTagLayout2.addTag(word);
93 | mTagLayout3.addTag(word);
94 | }
95 | });
96 | mTagDel.setTagClickListener(new TagView.OnTagClickListener() {
97 | @Override
98 | public void onTagClick(int position, String text, @TagView.TagMode int tagMode) {
99 | mTagLayout1.deleteTag(0);
100 | mTagLayout2.deleteTag(0);
101 | mTagLayout3.deleteTag(0);
102 | }
103 | });
104 |
105 | mTagLayout1.setTags(TagWordFactory.TAG_WORD);
106 | mTagLayout2.setTags(TagWordFactory.TAG_WORD);
107 | mTagLayout3.setTags(TagWordFactory.TAG_WORD);
108 | }
109 |
110 | @Override
111 | public void onTagLongClick(int position, String text, @TagView.TagMode int tagMode) {
112 | ToastUtils.showToast("长按:" + text);
113 | }
114 |
115 | }
116 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_tag_shape.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
15 |
16 |
20 |
21 |
26 |
27 |
33 |
34 |
40 |
41 |
46 |
47 |
53 |
54 |
60 |
61 |
66 |
67 |
73 |
74 |
80 |
81 |
82 |
83 |
84 |
88 |
89 |
93 |
94 |
108 |
109 |
124 |
125 |
126 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_tag_reverse.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
15 |
16 |
20 |
21 |
26 |
27 |
33 |
34 |
41 |
42 |
47 |
48 |
54 |
55 |
62 |
63 |
68 |
69 |
75 |
76 |
83 |
84 |
85 |
86 |
87 |
91 |
92 |
96 |
97 |
111 |
112 |
127 |
128 |
129 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_tag_choice.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
15 |
16 |
20 |
21 |
26 |
27 |
33 |
34 |
40 |
41 |
46 |
47 |
53 |
54 |
61 |
62 |
67 |
68 |
74 |
75 |
82 |
83 |
84 |
85 |
86 |
90 |
91 |
95 |
96 |
110 |
111 |
126 |
127 |
128 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_tag_change.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
15 |
16 |
20 |
21 |
26 |
27 |
33 |
34 |
40 |
41 |
46 |
47 |
53 |
54 |
63 |
64 |
69 |
70 |
76 |
77 |
83 |
84 |
85 |
86 |
87 |
91 |
92 |
96 |
97 |
111 |
112 |
127 |
128 |
129 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # TagLayout
2 | [](http://www.apache.org/licenses/LICENSE-2.0.html) [](https://jitpack.io/#Rukey7/TagLayout)
3 |
4 | ### 多功能的标签流布局
5 |
6 | ## Screenshot
7 |
8 | ##### 不同标签形状:
9 |
10 | 
11 |
12 | ##### 标签单选和多选模式:
13 |
14 | 
15 |
16 | ##### 标签编辑模式:
17 |
18 | 
19 |
20 | ##### 标签换一换模式:
21 |
22 | 
23 |
24 | ##### 单个标签的其它用法:
25 |
26 | 
27 |
28 | ## dependence
29 |
30 | 你需要在项目的根 `build.gradle` 加入如下JitPack仓库链接:
31 |
32 | ```gradle
33 | allprojects {
34 | repositories {
35 | ...
36 | maven { url 'https://jitpack.io' }
37 | }
38 | }
39 | ```
40 |
41 | 接着在你的需要依赖的Module的`build.gradle`加入依赖:
42 |
43 | ```gradle
44 | compile 'com.github.Rukey7:TagLayout:{lastest-version}'
45 | ```
46 |
47 | 其中 `{lastest-version}` 为最新的版本,你可以查看上面显示的jitpack版本信息,也可以到[jitpack.io](https://jitpack.io/#Rukey7/IjkPlayerView)仓库查看。
48 |
49 | ## Usage
50 |
51 |
52 | 在布局中直接使用:
53 |
54 | ```xml
55 |
56 |
57 |
63 |
64 |
65 |
74 | ```
75 |
76 | ### 属性设置
77 |
78 | 标签布局属性(有对应接口):
79 |
80 | |name|format|description|
81 | |:---:|:---:|:---:|
82 | | tag_layout_mode | enum | {normal,edit,change,single_choice,multi_choice}, 分别为正常、编辑、换一换、单选和多选等模式
83 | | tag_layout_shape | enum | {round_rect,arc,rect},标签形状分别为圆角矩形、圆弧形和直角矩形,默认round_rect
84 | | tag_layout_random_color | boolean | 随机颜色
85 | | tag_layout_press_feedback | boolean | 按压反馈效果
86 | | tag_layout_fit_num | integer | 设置一行固定显示几个标签
87 | | tag_layout_bg_color | color | 标签布局背景颜色
88 | | tag_layout_border_color | color | 标签布局边框颜色
89 | | tag_layout_border_radius | dimension | 标签布局边框圆角弧度
90 | | tag_layout_border_width | dimension | 标签布局边框大小
91 | | tag_layout_vertical_interval | dimension | 标签垂直间隔
92 | | tag_layout_horizontal_interval | dimension | 标签水平间隔
93 | | tag_view_bg_color | color | 标签背景颜色
94 | | tag_view_border_color | color | 标签边框颜色
95 | | tag_view_text_color | color | 标签字体颜色
96 | | tag_view_bg_color_check | color | 标签选中背景颜色
97 | | tag_view_border_color_check | color | 标签选中边框颜色
98 | | tag_view_text_color_check | color | 标签选中字体颜色
99 | | tag_view_border_width | dimension | 标签边框大小
100 | | tag_view_border_radius | dimension | 标签边框圆角弧度
101 | | tag_view_vertical_padding | dimension | 标签垂直填充
102 | | tag_view_horizontal_padding | dimension | 标签水平填充
103 | | tag_view_icon_padding | dimension | 标签icon和文字的间隔
104 | | tag_view_text_size | dimension | 标签字体大小(1.0.5由float改为dimension)
105 | | tag_layout_horizontal_reverse | boolean | 水平反向排列(RTL)
106 |
107 | 标签属性:
108 |
109 | |name|format|description|
110 | |:---:|:---:|:---:|
111 | | tag_mode | enum | {normal,check,icon_check_invisible,icon_check_change}, 分别为正常、可选中、选中图标消失和选中换图标等模式
112 | | tag_shape | enum | {round_rect,arc,rect},标签形状分别为圆角矩形、圆弧形和直角矩形,默认round_rect
113 | | tag_auto_check | boolean | 使能自动点击选中操作
114 | | tag_press_feedback | boolean | 按压反馈效果
115 | | tag_checked | boolean | 初始选中状态
116 | | tag_icon | reference | 标签图标
117 | | tag_icon_change | reference | 标签选中时替换的图标(icon_check_change模式)
118 | | tag_text_check | string | 标签选中时替换的字符
119 | | tag_bg_color | color | 标签背景颜色
120 | | tag_border_color | color | 标签边框颜色
121 | | tag_text_color | color | 标签字体颜色
122 | | tag_bg_color_check | color | 标签选中背景颜色
123 | | tag_border_color_check | color | 标签选中边框颜色
124 | | tag_text_color_check | color | 标签选中字体颜色
125 | | tag_border_width | dimension | 标签边框大小
126 | | tag_border_radius | dimension | 标签边框圆角弧度
127 | | tag_vertical_padding | dimension | 标签垂直填充
128 | | tag_horizontal_padding | dimension | 标签水平填充
129 | | tag_text | dimension | 标签icon和文字的间隔
130 | | tag_icon_padding | dimension | 标签icon和文字的间隔
131 | | tag_text | string | 标签字符
132 | | tag_text_size | dimension | 标签字体大小
133 | | tag_gravity | enum | 图标放置位置,只支持left和right
134 |
135 |
136 | ### ChangeLog
137 |
138 | ##### 1.0.4 -> 1.0.5
139 |
140 | 1、重写TagView直接继承View,简化了代码逻辑,不再支持TextView的android:text和android:textSize属性,替换为自定义的tag_text和tag_text_size属性;
141 |
142 | 2、增加了tag_gravity属性来设置Drawable的放置位置,只支持left和right;
143 |
144 | ##### 1.0.5 -> 1.0.6
145 |
146 | 1、添加水平反向排列属性(tag_layout_horizontal_reverse);
147 |
148 |
149 | License
150 | -------
151 |
152 | Copyright 2017 Rukey7
153 |
154 | Licensed under the Apache License, Version 2.0 (the "License");
155 | you may not use this file except in compliance with the License.
156 | You may obtain a copy of the License at
157 |
158 | http://www.apache.org/licenses/LICENSE-2.0
159 |
160 | Unless required by applicable law or agreed to in writing, software
161 | distributed under the License is distributed on an "AS IS" BASIS,
162 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
163 | See the License for the specific language governing permissions and
164 | limitations under the License.
165 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_tag_edit.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
15 |
16 |
20 |
21 |
26 |
27 |
33 |
34 |
41 |
42 |
47 |
48 |
54 |
55 |
64 |
65 |
70 |
71 |
77 |
78 |
83 |
84 |
85 |
86 |
87 |
91 |
92 |
96 |
97 |
111 |
112 |
129 |
130 |
145 |
146 |
147 |
--------------------------------------------------------------------------------
/app/src/main/java/com/dl7/taglayout/TagViewActivity.java:
--------------------------------------------------------------------------------
1 | package com.dl7.taglayout;
2 |
3 | import android.os.Bundle;
4 | import android.os.Handler;
5 | import android.os.Message;
6 | import android.util.SparseIntArray;
7 |
8 | import com.dl7.tag.TagView;
9 | import com.dl7.taglayout.drawable.CircleDrawable;
10 | import com.dl7.taglayout.drawable.MultiCircleDrawable;
11 | import com.dl7.taglayout.utils.RxHelper;
12 | import com.dl7.taglayout.utils.ToastUtils;
13 | import com.trello.rxlifecycle.android.ActivityEvent;
14 | import com.trello.rxlifecycle.components.support.RxAppCompatActivity;
15 |
16 | import rx.Subscriber;
17 |
18 | public class TagViewActivity extends RxAppCompatActivity {
19 |
20 | private TagView mTagGoodOrBad;
21 | private TagView mTagRightOrError;
22 | private TagView mTagSmileOrCry;
23 | private TagView mTagGetCode;
24 | private TagView mTagSkip;
25 | private TagView mTagSkip2;
26 | private TagView mTagSingleCircle;
27 | private TagView mTagMultiCircle;
28 |
29 | private int mCountNum = 5;
30 | private Handler mHandler = new Handler() {
31 | @Override
32 | public void handleMessage(Message msg) {
33 | super.handleMessage(msg);
34 | }
35 | };
36 | private Runnable mRunnable = new Runnable() {
37 | @Override
38 | public void run() {
39 | mTagSkip2.setText("跳过 " + mCountNum);
40 | mTagSkip.setText((mCountNum--) + " 跳过");
41 | if (mCountNum == 0) {
42 | mCountNum = 5;
43 | }
44 | mHandler.postDelayed(this, 1000);
45 | }
46 | };
47 | private SparseIntArray mTimeSparse = new SparseIntArray();
48 |
49 | @Override
50 | protected void onCreate(Bundle savedInstanceState) {
51 | super.onCreate(savedInstanceState);
52 | setContentView(R.layout.activity_tag_view);
53 | initView();
54 | }
55 |
56 | private void initView() {
57 | mTagGoodOrBad = (TagView) findViewById(R.id.tag_good_or_bad);
58 | mTagRightOrError = (TagView) findViewById(R.id.tag_right_or_error);
59 | mTagSmileOrCry = (TagView) findViewById(R.id.tag_smile_or_cry);
60 | mTagGetCode = (TagView) findViewById(R.id.tag_get_code);
61 | mTagSkip = (TagView) findViewById(R.id.tag_skip);
62 | mTagSkip2 = (TagView) findViewById(R.id.tag_skip_2);
63 | mTagGoodOrBad.setTagClickListener(new TagView.OnTagClickListener() {
64 | @Override
65 | public void onTagClick(int position, String text, @TagView.TagMode int tagMode) {
66 | if (_isClickedNow(R.id.tag_good_or_bad)) {
67 | return;
68 | }
69 | mHandler.postDelayed(new Runnable() {
70 | @Override
71 | public void run() {
72 | mTagGoodOrBad.setChecked(!mTagGoodOrBad.isChecked());
73 | }
74 | }, 2000);
75 | }
76 | });
77 | mTagRightOrError.setTagClickListener(new TagView.OnTagClickListener() {
78 | @Override
79 | public void onTagClick(int position, String text, @TagView.TagMode int tagMode) {
80 | if (_isClickedNow(R.id.tag_right_or_error)) {
81 | return;
82 | }
83 | mHandler.postDelayed(new Runnable() {
84 | @Override
85 | public void run() {
86 | mTagRightOrError.setChecked(!mTagRightOrError.isChecked());
87 | }
88 | }, 2000);
89 | }
90 | });
91 | mTagSmileOrCry.setTagClickListener(new TagView.OnTagClickListener() {
92 | @Override
93 | public void onTagClick(int position, String text, @TagView.TagMode int tagMode) {
94 | if (_isClickedNow(R.id.tag_smile_or_cry)) {
95 | return;
96 | }
97 | mHandler.postDelayed(new Runnable() {
98 | @Override
99 | public void run() {
100 | mTagSmileOrCry.setChecked(!mTagSmileOrCry.isChecked());
101 | }
102 | }, 2000);
103 | }
104 | });
105 |
106 | mTagGetCode.setTagClickListener(new TagView.OnTagClickListener() {
107 | @Override
108 | public void onTagClick(int position, String text, @TagView.TagMode int tagMode) {
109 | if (!mTagGetCode.isChecked()) {
110 | mTagGetCode.setChecked(true);
111 | RxHelper.countdown(10)
112 | .compose(TagViewActivity.this.bindUntilEvent(ActivityEvent.DESTROY))
113 | .subscribe(new Subscriber() {
114 | @Override
115 | public void onCompleted() {
116 | mTagGetCode.setChecked(false);
117 | mTagGetCode.setText("重新获取");
118 | }
119 |
120 | @Override
121 | public void onError(Throwable e) {
122 | mTagGetCode.setChecked(false);
123 | mTagGetCode.setText("重新获取");
124 | }
125 |
126 | @Override
127 | public void onNext(Integer time) {
128 | mTagGetCode.setTextChecked("已发送(" + time + ")");
129 | }
130 | });
131 | }
132 | }
133 | });
134 |
135 | mTagSkip.setTagClickListener(new TagView.OnTagClickListener() {
136 | @Override
137 | public void onTagClick(int position, String text, @TagView.TagMode int tagMode) {
138 | ToastUtils.showToast(text);
139 | }
140 | });
141 | mTagSkip2.setTagClickListener(new TagView.OnTagClickListener() {
142 | @Override
143 | public void onTagClick(int position, String text, @TagView.TagMode int tagMode) {
144 | ToastUtils.showToast(text);
145 | }
146 | });
147 | mHandler.postDelayed(mRunnable, 1000);
148 | //
149 | mTagSingleCircle = (TagView) findViewById(R.id.tag_single_circle);
150 | mTagSingleCircle.setDecorateIcon(new CircleDrawable());
151 | mTagMultiCircle = (TagView) findViewById(R.id.tag_multi_circle);
152 | mTagMultiCircle.setDecorateIcon(new MultiCircleDrawable());
153 | }
154 |
155 | private boolean _isClickedNow(int id) {
156 | int lastTime = mTimeSparse.get(id);
157 | boolean isClickedNow = false;
158 | int curTime = (int) (System.currentTimeMillis() % 1000000);
159 | if (curTime - lastTime < 2000) {
160 | isClickedNow = true;
161 | }
162 | mTimeSparse.put(id, curTime);
163 | return isClickedNow;
164 | }
165 |
166 | @Override
167 | protected void onDestroy() {
168 | mHandler.removeCallbacksAndMessages(null);
169 | super.onDestroy();
170 | }
171 | }
172 |
--------------------------------------------------------------------------------
/taglayout/src/main/java/com/dl7/tag/TagEditView.java:
--------------------------------------------------------------------------------
1 | package com.dl7.tag;
2 |
3 | import android.content.Context;
4 | import android.graphics.Canvas;
5 | import android.graphics.Color;
6 | import android.graphics.DashPathEffect;
7 | import android.graphics.Paint;
8 | import android.graphics.PathEffect;
9 | import android.graphics.RectF;
10 | import android.text.TextUtils;
11 | import android.text.method.ArrowKeyMovementMethod;
12 | import android.util.AttributeSet;
13 | import android.view.Gravity;
14 | import android.view.KeyEvent;
15 | import android.view.ViewParent;
16 | import android.view.inputmethod.EditorInfo;
17 | import android.view.inputmethod.InputMethodManager;
18 | import android.widget.TextView;
19 |
20 | import com.dl7.tag.utils.MeasureUtils;
21 |
22 | import static com.dl7.tag.TagView.INVALID_VALUE;
23 | import static com.dl7.tag.TagView.SHAPE_ARC;
24 | import static com.dl7.tag.TagView.SHAPE_RECT;
25 | import static com.dl7.tag.TagView.SHAPE_ROUND_RECT;
26 |
27 | /**
28 | * Created by long on 2017/4/21.
29 | */
30 |
31 | public class TagEditView extends TextView {
32 |
33 | private Paint mBorderPaint;
34 | // 虚线路径
35 | private PathEffect mPathEffect = new DashPathEffect(new float[]{10, 5}, 0);
36 | // 边框矩形
37 | private RectF mRect;
38 | // 边框大小
39 | private float mBorderWidth;
40 | // 显示外形
41 | private int mTagShape = SHAPE_ROUND_RECT;
42 | // 边框角半径
43 | private float mRadius;
44 | // 边框颜色
45 | private int mBorderColor;
46 | // 字体水平空隙
47 | private int mHorizontalPadding;
48 | // 字体垂直空隙
49 | private int mVerticalPadding;
50 |
51 | public TagEditView(Context context) {
52 | super(context);
53 | _init(context);
54 | }
55 |
56 | public TagEditView(Context context, AttributeSet attrs) {
57 | super(context, attrs);
58 | _init(context);
59 | }
60 |
61 | private void _init(Context context) {
62 | mHorizontalPadding = (int) MeasureUtils.dp2px(context, 5f);
63 | mVerticalPadding = (int) MeasureUtils.dp2px(context, 5f);
64 | setPadding(mHorizontalPadding, mVerticalPadding, mHorizontalPadding, mVerticalPadding);
65 | mRect = new RectF();
66 | mBorderColor = Color.parseColor("#ff333333");
67 | mBorderPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
68 | mBorderPaint.setStyle(Paint.Style.STROKE);
69 | mBorderPaint.setColor(mBorderColor);
70 | mBorderWidth = MeasureUtils.dp2px(context, 0.5f);
71 | mRadius = MeasureUtils.dp2px(context, 5f);
72 | // 设置字体占中
73 | setGravity(Gravity.CENTER);
74 | _initEditMode();
75 | }
76 |
77 | @Override
78 | protected boolean getDefaultEditable() {
79 | return true;
80 | }
81 |
82 | @Override
83 | protected void onSizeChanged(int w, int h, int oldw, int oldh) {
84 | super.onSizeChanged(w, h, oldw, oldh);
85 | // 设置矩形边框
86 | mRect.set(mBorderWidth, mBorderWidth, w - mBorderWidth, h - mBorderWidth);
87 | }
88 |
89 | @Override
90 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
91 | ViewParent parent = getParent();
92 | if (parent instanceof TagLayout) {
93 | int fitTagNum = ((TagLayout) getParent()).getFitTagNum();
94 | if (fitTagNum != INVALID_VALUE) {
95 | int availableWidth = ((TagLayout) getParent()).getAvailableWidth();
96 | int horizontalInterval = ((TagLayout) getParent()).getHorizontalInterval();
97 | int fitWidth = (availableWidth - (fitTagNum - 1) * horizontalInterval) / fitTagNum;
98 | widthMeasureSpec = MeasureSpec.makeMeasureSpec(fitWidth, MeasureSpec.EXACTLY);
99 | }
100 | }
101 | super.onMeasure(widthMeasureSpec, heightMeasureSpec);
102 | }
103 |
104 | @Override
105 | protected void onDraw(Canvas canvas) {
106 | float radius = mRadius;
107 | if (mTagShape == SHAPE_ARC) {
108 | radius = mRect.height() / 2;
109 | } else if (mTagShape == SHAPE_RECT) {
110 | radius = 0;
111 | }
112 | canvas.drawRoundRect(mRect, radius, radius, mBorderPaint);
113 | super.onDraw(canvas);
114 | }
115 |
116 | /**
117 | * 初始化编辑模式
118 | */
119 | private void _initEditMode() {
120 | setClickable(true);
121 | setFocusable(true);
122 | setFocusableInTouchMode(true);
123 | setHint("添加标签");
124 | mBorderPaint.setPathEffect(mPathEffect);
125 | setHintTextColor(Color.parseColor("#ffaaaaaa"));
126 | setMovementMethod(ArrowKeyMovementMethod.getInstance());
127 | requestFocus();
128 | setOnEditorActionListener(new OnEditorActionListener() {
129 | @Override
130 | public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
131 | if (actionId == EditorInfo.IME_NULL
132 | && (event != null && event.getKeyCode() == KeyEvent.KEYCODE_ENTER
133 | && event.getAction() == KeyEvent.ACTION_DOWN)) {
134 | if (!TextUtils.isEmpty(getText())) {
135 | ((TagLayout) getParent()).addTag(getText().toString());
136 | setText("");
137 | _closeSoftInput();
138 | }
139 | return true;
140 | }
141 | return false;
142 | }
143 | });
144 | }
145 |
146 | /**
147 | * 离开编辑模式
148 | */
149 | public void exitEditMode() {
150 | clearFocus();
151 | setFocusable(false);
152 | setFocusableInTouchMode(false);
153 | setHint(null);
154 | setMovementMethod(null);
155 | _closeSoftInput();
156 | }
157 |
158 | /**
159 | * 关闭软键盘
160 | */
161 | private void _closeSoftInput() {
162 | InputMethodManager inputMethodManager = (InputMethodManager) getContext()
163 | .getSystemService(Context.INPUT_METHOD_SERVICE);
164 | //如果软键盘已经开启
165 | if (inputMethodManager.isActive()) {
166 | inputMethodManager.hideSoftInputFromWindow(getWindowToken(), InputMethodManager.HIDE_NOT_ALWAYS);
167 | }
168 | }
169 |
170 | /**
171 | * ==================================== 开放接口 ====================================
172 | */
173 |
174 | public void setBorderColor(int borderColor) {
175 | mBorderColor = borderColor;
176 | mBorderPaint.setColor(mBorderColor);
177 | }
178 |
179 | public void setBorderWidth(float borderWidth) {
180 | mBorderWidth = borderWidth;
181 | }
182 |
183 | public void setRadius(float radius) {
184 | mRadius = radius;
185 | }
186 |
187 | public void setHorizontalPadding(int horizontalPadding) {
188 | mHorizontalPadding = horizontalPadding;
189 | setPadding(mHorizontalPadding, mVerticalPadding, mHorizontalPadding, mVerticalPadding);
190 | }
191 |
192 | public void setVerticalPadding(int verticalPadding) {
193 | mVerticalPadding = verticalPadding;
194 | setPadding(mHorizontalPadding, mVerticalPadding, mHorizontalPadding, mVerticalPadding);
195 | }
196 |
197 | @Override
198 | public void setTextColor(int color) {
199 | super.setTextColor(color);
200 | setHintTextColor(color);
201 | }
202 |
203 | /**
204 | * 调用这些接口进行属性设置如果最后可能会改变按钮的大小的话最后调用一下这个接口,以刷新界面,建议属性直接在布局里设置
205 | * 只需要回调onDraw()的话调用invalidate()就可以了
206 | */
207 | public void updateView() {
208 | requestLayout();
209 | invalidate();
210 | }
211 | }
212 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_tag_view.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
16 |
17 |
23 |
24 |
28 |
29 |
42 |
43 |
57 |
58 |
72 |
73 |
74 |
79 |
80 |
86 |
87 |
91 |
92 |
106 |
107 |
122 |
123 |
138 |
139 |
140 |
145 |
146 |
152 |
153 |
157 |
158 |
173 |
174 |
190 |
191 |
204 |
205 |
206 |
211 |
212 |
218 |
219 |
223 |
224 |
234 |
235 |
249 |
250 |
262 |
263 |
264 |
269 |
270 |
276 |
277 |
281 |
282 |
294 |
295 |
311 |
312 |
328 |
329 |
330 |
335 |
336 |
342 |
343 |
347 |
348 |
362 |
363 |
381 |
382 |
400 |
401 |
402 |
407 |
408 |
414 |
415 |
419 |
420 |
436 |
437 |
450 |
451 |
468 |
469 |
470 |
471 |
476 |
477 |
483 |
484 |
488 |
489 |
503 |
504 |
522 |
523 |
524 |
525 |
--------------------------------------------------------------------------------
/taglayout/src/main/java/com/dl7/tag/TagLayout.java:
--------------------------------------------------------------------------------
1 | package com.dl7.tag;
2 |
3 | import android.content.Context;
4 | import android.content.res.TypedArray;
5 | import android.graphics.Canvas;
6 | import android.graphics.Color;
7 | import android.graphics.Paint;
8 | import android.graphics.RectF;
9 | import android.support.v4.content.ContextCompat;
10 | import android.util.AttributeSet;
11 | import android.util.SparseBooleanArray;
12 | import android.view.View;
13 | import android.view.ViewGroup;
14 |
15 | import com.dl7.tag.utils.ColorsFactory;
16 | import com.dl7.tag.utils.MeasureUtils;
17 |
18 | import java.util.ArrayList;
19 | import java.util.List;
20 |
21 |
22 | /**
23 | * Created by long on 2016/7/20.
24 | * TagLayout
25 | */
26 | public class TagLayout extends ViewGroup {
27 |
28 | private Paint mPaint;
29 | // 背景色
30 | private int mBgColor;
31 | // 边框颜色
32 | private int mBorderColor;
33 | // 边框大小
34 | private float mBorderWidth;
35 | // 边框角半径
36 | private float mRadius;
37 | // Tag之间的垂直间隙
38 | private int mVerticalInterval;
39 | // Tag之间的水平间隙
40 | private int mHorizontalInterval;
41 | // 边框矩形
42 | private RectF mRect;
43 | // 可用的最大宽度
44 | private int mAvailableWidth;
45 |
46 | private int mTagBgColor;
47 | private int mTagBorderColor;
48 | private int mTagTextColor;
49 | private int mTagBgColorCheck;
50 | private int mTagBorderColorCheck;
51 | private int mTagTextColorCheck;
52 | private float mTagBorderWidth;
53 | private float mTagTextSize;
54 | private float mTagRadius;
55 | private int mTagHorizontalPadding;
56 | private int mTagVerticalPadding;
57 | private TagView.OnTagClickListener mTagClickListener;
58 | private TagView.OnTagLongClickListener mTagLongClickListener;
59 | private TagView.OnTagCheckListener mTagCheckListener;
60 | private TagView.OnTagCheckListener mInsideTagCheckListener;
61 | // 这个用来保存设置监听器之前的TagView
62 | private List mTagViews = new ArrayList<>();
63 | private SparseBooleanArray mCheckSparseArray = new SparseBooleanArray();
64 | // 显示模式
65 | private int mTagShape;
66 | private int mFitTagNum;
67 | private int mIconPadding;
68 | private boolean mIsPressFeedback;
69 | // 显示类型
70 | private int mTagMode;
71 | // 固定状态的TagView
72 | private TagView mFitTagView;
73 | private TagEditView mFitTagEditView;
74 | // 使能随机颜色
75 | private boolean mEnableRandomColor;
76 | // 是否反转水平布局
77 | private boolean mIsHorizontalReverse;
78 |
79 |
80 | public TagLayout(Context context) {
81 | this(context, null);
82 | }
83 |
84 | public TagLayout(Context context, AttributeSet attrs) {
85 | this(context, attrs, -1);
86 | }
87 |
88 | public TagLayout(Context context, AttributeSet attrs, int defStyleAttr) {
89 | super(context, attrs, defStyleAttr);
90 | _init(context, attrs, defStyleAttr);
91 | }
92 |
93 | private void _init(Context context, AttributeSet attrs, int defStyleAttr) {
94 | mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
95 | mRect = new RectF();
96 | mTagTextSize = MeasureUtils.sp2px(context, 13.0f);
97 |
98 | final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.TagLayout);
99 | try {
100 | mTagMode = a.getInteger(R.styleable.TagLayout_tag_layout_mode, TagView.MODE_NORMAL);
101 | mTagShape = a.getInteger(R.styleable.TagLayout_tag_layout_shape, TagView.SHAPE_ROUND_RECT);
102 | mIsPressFeedback = a.getBoolean(R.styleable.TagLayout_tag_layout_press_feedback, false);
103 | mEnableRandomColor = a.getBoolean(R.styleable.TagLayout_tag_layout_random_color, false);
104 | mFitTagNum = a.getInteger(R.styleable.TagLayout_tag_layout_fit_num, TagView.INVALID_VALUE);
105 | mBgColor = a.getColor(R.styleable.TagLayout_tag_layout_bg_color, Color.WHITE);
106 | mBorderColor = a.getColor(R.styleable.TagLayout_tag_layout_border_color, Color.WHITE);
107 | mBorderWidth = a.getDimension(R.styleable.TagLayout_tag_layout_border_width, MeasureUtils.dp2px(context, 0.5f));
108 | mRadius = a.getDimension(R.styleable.TagLayout_tag_layout_border_radius, MeasureUtils.dp2px(context, 5f));
109 | mHorizontalInterval = (int) a.getDimension(R.styleable.TagLayout_tag_layout_horizontal_interval, MeasureUtils.dp2px(context, 5f));
110 | mVerticalInterval = (int) a.getDimension(R.styleable.TagLayout_tag_layout_vertical_interval, MeasureUtils.dp2px(context, 5f));
111 |
112 | mTagBgColor = a.getColor(R.styleable.TagLayout_tag_view_bg_color, Color.WHITE);
113 | mTagBorderColor = a.getColor(R.styleable.TagLayout_tag_view_border_color, Color.parseColor("#ff333333"));
114 | mTagTextColor = a.getColor(R.styleable.TagLayout_tag_view_text_color, Color.parseColor("#ff666666"));
115 | if (mIsPressFeedback || mTagMode == TagView.MODE_SINGLE_CHOICE || mTagMode == TagView.MODE_MULTI_CHOICE) {
116 | mTagBgColorCheck = a.getColor(R.styleable.TagLayout_tag_view_bg_color_check, mTagTextColor);
117 | mTagBorderColorCheck = a.getColor(R.styleable.TagLayout_tag_view_border_color_check, mTagTextColor);
118 | mTagTextColorCheck = a.getColor(R.styleable.TagLayout_tag_view_text_color_check, Color.WHITE);
119 | } else {
120 | mTagBgColorCheck = a.getColor(R.styleable.TagLayout_tag_view_bg_color_check, mTagBgColor);
121 | mTagBorderColorCheck = a.getColor(R.styleable.TagLayout_tag_view_border_color_check, mTagBorderColor);
122 | mTagTextColorCheck = a.getColor(R.styleable.TagLayout_tag_view_text_color_check, mTagTextColor);
123 | }
124 | mTagBorderWidth = a.getDimension(R.styleable.TagLayout_tag_view_border_width, MeasureUtils.dp2px(context, 0.5f));
125 | mTagTextSize = a.getDimension(R.styleable.TagLayout_tag_view_text_size, mTagTextSize);
126 | mTagRadius = a.getDimension(R.styleable.TagLayout_tag_view_border_radius, MeasureUtils.dp2px(context, 5f));
127 | mTagHorizontalPadding = (int) a.getDimension(R.styleable.TagLayout_tag_view_horizontal_padding, MeasureUtils.dp2px(context, 5f));
128 | mTagVerticalPadding = (int) a.getDimension(R.styleable.TagLayout_tag_view_vertical_padding, MeasureUtils.dp2px(context, 5f));
129 | mIconPadding = (int) a.getDimension(R.styleable.TagLayout_tag_view_icon_padding, MeasureUtils.dp2px(context, 3f));
130 | mIsHorizontalReverse = a.getBoolean(R.styleable.TagLayout_tag_layout_horizontal_reverse, false);
131 | } finally {
132 | a.recycle();
133 | }
134 | // 如果想要自己绘制内容,则必须设置这个标志位为false,否则onDraw()方法不会调用
135 | setWillNotDraw(false);
136 | setPadding(mHorizontalInterval, mVerticalInterval, mHorizontalInterval, mVerticalInterval);
137 | // 设置对应模式
138 | if (mTagMode == TagView.MODE_CHANGE) {
139 | mFitTagView = _initTagView("换一换", TagView.MODE_CHANGE);
140 | addView(mFitTagView);
141 | } else if (mTagMode == TagView.MODE_EDIT) {
142 | _initTagEditView();
143 | addView(mFitTagEditView);
144 | } else if (mTagMode == TagView.MODE_SINGLE_CHOICE || mTagMode == TagView.MODE_MULTI_CHOICE) {
145 | mIsPressFeedback = true;
146 | mInsideTagCheckListener = new TagView.OnTagCheckListener() {
147 | @Override
148 | public void onTagCheck(int position, String text, boolean isChecked) {
149 | if (mTagCheckListener != null) {
150 | mTagCheckListener.onTagCheck(position, text, isChecked);
151 | }
152 | for (int i = 0; i < mTagViews.size(); i++) {
153 | if (mTagViews.get(i).getText().equals(text)) {
154 | mCheckSparseArray.put(i, isChecked);
155 | } else if (mTagMode == TagView.MODE_SINGLE_CHOICE && mCheckSparseArray.get(i)) {
156 | mTagViews.get(i).cleanTagCheckStatus();
157 | mCheckSparseArray.put(i, false);
158 | }
159 | }
160 | }
161 | };
162 | }
163 | }
164 |
165 | @Override
166 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
167 | super.onMeasure(widthMeasureSpec, heightMeasureSpec);
168 | int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
169 | int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
170 | int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
171 | // 计算可用宽度,为测量宽度减去左右padding值,这个放在measureChildren前面,在子视图中会用到这个参数
172 | mAvailableWidth = widthSpecSize - getPaddingLeft() - getPaddingRight();
173 | // 测量子视图
174 | measureChildren(widthMeasureSpec, heightMeasureSpec);
175 | int childCount = getChildCount();
176 | int tmpWidth = 0;
177 | int measureHeight = 0;
178 | int maxLineHeight = 0;
179 | for (int i = 0; i < childCount; i++) {
180 | View child = getChildAt(i);
181 | // 记录该行的最大高度
182 | if (maxLineHeight == 0) {
183 | maxLineHeight = child.getMeasuredHeight();
184 | } else {
185 | maxLineHeight = Math.max(maxLineHeight, child.getMeasuredHeight());
186 | }
187 | // 统计该行TagView的总宽度
188 | tmpWidth += child.getMeasuredWidth() + mHorizontalInterval;
189 | // 如果超过可用宽度则换行
190 | if (tmpWidth - mHorizontalInterval > mAvailableWidth) {
191 | // 统计TagGroup的测量高度,要加上垂直间隙
192 | measureHeight += maxLineHeight + mVerticalInterval;
193 | // 重新赋值
194 | tmpWidth = child.getMeasuredWidth() + mHorizontalInterval;
195 | maxLineHeight = child.getMeasuredHeight();
196 | }
197 | }
198 | // 统计TagGroup的测量高度,加上最后一行
199 | measureHeight += maxLineHeight;
200 |
201 | // 设置测量宽高,记得算上padding
202 | if (childCount == 0) {
203 | setMeasuredDimension(0, 0);
204 | } else if (heightSpecMode == MeasureSpec.UNSPECIFIED || heightSpecMode == MeasureSpec.AT_MOST) {
205 | setMeasuredDimension(widthSpecSize, measureHeight + getPaddingTop() + getPaddingBottom());
206 | } else {
207 | setMeasuredDimension(widthSpecSize, heightSpecSize);
208 | }
209 | }
210 |
211 | @Override
212 | protected void onLayout(boolean changed, int l, int t, int r, int b) {
213 | int childCount = getChildCount();
214 | if (childCount <= 0) {
215 | return;
216 | }
217 |
218 | mAvailableWidth = getMeasuredWidth() - getPaddingLeft() - getPaddingRight();
219 | // 当前布局使用的top坐标
220 | int curTop = getPaddingTop();
221 | // 当前布局使用的left坐标
222 | int curLeft;
223 | final int maxRight = mAvailableWidth + getPaddingLeft();
224 | if (mIsHorizontalReverse) {
225 | curLeft = maxRight;
226 | } else {
227 | curLeft = getPaddingLeft();
228 | }
229 | int maxHeight = 0;
230 | for (int i = 0; i < childCount; i++) {
231 | View child = getChildAt(i);
232 |
233 | if (maxHeight == 0) {
234 | maxHeight = child.getMeasuredHeight();
235 | } else {
236 | maxHeight = Math.max(maxHeight, child.getMeasuredHeight());
237 | }
238 |
239 | int width = child.getMeasuredWidth();
240 | int height = child.getMeasuredHeight();
241 | // 超过一行做换行操作
242 | if (mIsHorizontalReverse) {
243 | curLeft -= width;
244 | if (getPaddingLeft() > curLeft) {
245 | curLeft = maxRight - width;
246 | curLeft = Math.max(curLeft, getPaddingLeft());
247 | // 计算top坐标,要加上垂直间隙
248 | curTop += maxHeight + mVerticalInterval;
249 | maxHeight = child.getMeasuredHeight();
250 | }
251 | // 设置子视图布局
252 | child.layout(curLeft, curTop, curLeft + width, curTop + height);
253 | curLeft -= mHorizontalInterval;
254 | } else {
255 | if (width + curLeft > maxRight) {
256 | curLeft = getPaddingLeft();
257 | // 计算top坐标,要加上垂直间隙
258 | curTop += maxHeight + mVerticalInterval;
259 | maxHeight = child.getMeasuredHeight();
260 | }
261 | // 设置子视图布局
262 | child.layout(curLeft, curTop, curLeft + width, curTop + height);
263 | // 计算left坐标,要加上水平间隙
264 | curLeft += width + mHorizontalInterval;
265 | }
266 | }
267 | }
268 |
269 | @Override
270 | protected void onSizeChanged(int w, int h, int oldw, int oldh) {
271 | super.onSizeChanged(w, h, oldw, oldh);
272 | mRect.set(mBorderWidth, mBorderWidth, w - mBorderWidth, h - mBorderWidth);
273 | }
274 |
275 | @Override
276 | protected void onDraw(Canvas canvas) {
277 | // super.onDraw(canvas);
278 | // 绘制背景
279 | mPaint.setStyle(Paint.Style.FILL);
280 | mPaint.setColor(mBgColor);
281 | canvas.drawRoundRect(mRect, mRadius, mRadius, mPaint);
282 | // 绘制边框
283 | mPaint.setStyle(Paint.Style.STROKE);
284 | mPaint.setStrokeWidth(mBorderWidth);
285 | mPaint.setColor(mBorderColor);
286 | canvas.drawRoundRect(mRect, mRadius, mRadius, mPaint);
287 | }
288 |
289 | /**
290 | * ==================================== 设置TagGroup ====================================
291 | */
292 |
293 | public int getBgColor() {
294 | return mBgColor;
295 | }
296 |
297 | public void setBgColor(int bgColor) {
298 | mBgColor = bgColor;
299 | }
300 |
301 | public int getBorderColor() {
302 | return mBorderColor;
303 | }
304 |
305 | public void setBorderColor(int borderColor) {
306 | mBorderColor = borderColor;
307 | }
308 |
309 | public float getBorderWidth() {
310 | return mBorderWidth;
311 | }
312 |
313 | public void setBorderWidth(float borderWidth) {
314 | mBorderWidth = MeasureUtils.dp2px(getContext(), borderWidth);
315 | }
316 |
317 | public float getRadius() {
318 | return mRadius;
319 | }
320 |
321 | public void setRadius(float radius) {
322 | mRadius = radius;
323 | }
324 |
325 | public int getVerticalInterval() {
326 | return mVerticalInterval;
327 | }
328 |
329 | public void setVerticalInterval(int verticalInterval) {
330 | mVerticalInterval = verticalInterval;
331 | }
332 |
333 | public int getHorizontalInterval() {
334 | return mHorizontalInterval;
335 | }
336 |
337 | public void setHorizontalInterval(int horizontalInterval) {
338 | mHorizontalInterval = horizontalInterval;
339 | }
340 |
341 | protected int getAvailableWidth() {
342 | return mAvailableWidth;
343 | }
344 |
345 | public int getFitTagNum() {
346 | return mFitTagNum;
347 | }
348 |
349 | public void setFitTagNum(int fitTagNum) {
350 | mFitTagNum = fitTagNum;
351 | }
352 |
353 | /**
354 | * ==================================== 设置TagView ====================================
355 | */
356 |
357 | private void _initTagEditView() {
358 | mFitTagEditView = new TagEditView(getContext());
359 | if (mEnableRandomColor) {
360 | int[] color = ColorsFactory.provideColor();
361 | mFitTagEditView.setBorderColor(color[0]);
362 | mFitTagEditView.setTextColor(color[1]);
363 | } else {
364 | mFitTagEditView = new TagEditView(getContext());
365 | mFitTagEditView.setBorderColor(mTagBorderColor);
366 | mFitTagEditView.setTextColor(mTagTextColor);
367 | }
368 | mFitTagEditView.setBorderWidth(mTagBorderWidth);
369 | mFitTagEditView.setRadius(mTagRadius);
370 | mFitTagEditView.setHorizontalPadding(mTagHorizontalPadding);
371 | mFitTagEditView.setVerticalPadding(mTagVerticalPadding);
372 | mFitTagEditView.setTextSize(MeasureUtils.px2sp(getContext(), mTagTextSize));
373 | mFitTagEditView.updateView();
374 | }
375 |
376 | private TagView _initTagView(String text, @TagView.TagMode int tagMode) {
377 | TagView tagView = new TagView(getContext(), text);
378 | if (mEnableRandomColor) {
379 | _setTagRandomColors(tagView);
380 | } else {
381 | tagView.setBgColorLazy(mTagBgColor);
382 | tagView.setBorderColorLazy(mTagBorderColor);
383 | tagView.setTextColorLazy(mTagTextColor);
384 | tagView.setBgColorCheckedLazy(mTagBgColorCheck);
385 | tagView.setBorderColorCheckedLazy(mTagBorderColorCheck);
386 | tagView.setTextColorCheckedLazy(mTagTextColorCheck);
387 | }
388 | tagView.setBorderWidthLazy(mTagBorderWidth);
389 | tagView.setRadiusLazy(mTagRadius);
390 | tagView.setTextSizeLazy(mTagTextSize);
391 | tagView.setHorizontalPaddingLazy(mTagHorizontalPadding);
392 | tagView.setVerticalPaddingLazy(mTagVerticalPadding);
393 | tagView.setPressFeedback(mIsPressFeedback);
394 | tagView.setTagClickListener(mTagClickListener);
395 | tagView.setTagLongClickListener(mTagLongClickListener);
396 | tagView.setTagCheckListener(mInsideTagCheckListener);
397 | tagView.setTagShapeLazy(mTagShape);
398 | tagView.setTagModeLazy(tagMode);
399 | tagView.setIconPaddingLazy(mIconPadding);
400 | tagView.updateView();
401 | mTagViews.add(tagView);
402 | tagView.setTag(mTagViews.size() - 1);
403 | return tagView;
404 | }
405 |
406 | private void _setTagRandomColors(TagView tagView) {
407 | int[] color = ColorsFactory.provideColor();
408 | if (mIsPressFeedback) {
409 | tagView.setTextColorLazy(color[1]);
410 | tagView.setBgColorLazy(Color.WHITE);
411 | tagView.setBgColorCheckedLazy(color[0]);
412 | tagView.setBorderColorCheckedLazy(color[0]);
413 | tagView.setTextColorCheckedLazy(Color.WHITE);
414 | } else {
415 | tagView.setBgColorLazy(color[1]);
416 | tagView.setTextColorLazy(mTagTextColor);
417 | tagView.setBgColorCheckedLazy(color[1]);
418 | tagView.setBorderColorCheckedLazy(color[0]);
419 | tagView.setTextColorCheckedLazy(mTagTextColor);
420 | }
421 | tagView.setBorderColorLazy(color[0]);
422 | }
423 |
424 | public int getTagBgColor() {
425 | return mTagBgColor;
426 | }
427 |
428 | public void setTagBgColor(int tagBgColor) {
429 | mTagBgColor = tagBgColor;
430 | }
431 |
432 | public int getTagBorderColor() {
433 | return mTagBorderColor;
434 | }
435 |
436 | public void setTagBorderColor(int tagBorderColor) {
437 | mTagBorderColor = tagBorderColor;
438 | }
439 |
440 | public int getTagTextColor() {
441 | return mTagTextColor;
442 | }
443 |
444 | public void setTagTextColor(int tagTextColor) {
445 | mTagTextColor = tagTextColor;
446 | }
447 |
448 | public float getTagBorderWidth() {
449 | return mTagBorderWidth;
450 | }
451 |
452 | public void setTagBorderWidth(float tagBorderWidth) {
453 | mTagBorderWidth = MeasureUtils.dp2px(getContext(), tagBorderWidth);
454 | if (mFitTagView != null) {
455 | mFitTagView.setBorderWidth(mTagBorderWidth);
456 | }
457 | }
458 |
459 | public float getTagTextSize() {
460 | return mTagTextSize;
461 | }
462 |
463 | public void setTagTextSize(float tagTextSize) {
464 | mTagTextSize = tagTextSize;
465 | if (mFitTagView != null) {
466 | mFitTagView.setTextSize(tagTextSize);
467 | }
468 | }
469 |
470 | public float getTagRadius() {
471 | return mTagRadius;
472 | }
473 |
474 | public void setTagRadius(float tagRadius) {
475 | mTagRadius = tagRadius;
476 | if (mFitTagView != null) {
477 | mFitTagView.setRadius(mTagRadius);
478 | }
479 | }
480 |
481 | public int getTagHorizontalPadding() {
482 | return mTagHorizontalPadding;
483 | }
484 |
485 | public void setTagHorizontalPadding(int tagHorizontalPadding) {
486 | mTagHorizontalPadding = tagHorizontalPadding;
487 | if (mFitTagView != null) {
488 | mFitTagView.setHorizontalPadding(mTagHorizontalPadding);
489 | }
490 | }
491 |
492 | public int getTagVerticalPadding() {
493 | return mTagVerticalPadding;
494 | }
495 |
496 | public void setTagVerticalPadding(int tagVerticalPadding) {
497 | mTagVerticalPadding = tagVerticalPadding;
498 | if (mFitTagView != null) {
499 | mFitTagView.setVerticalPadding(mTagVerticalPadding);
500 | }
501 | }
502 |
503 | public boolean isPressFeedback() {
504 | return mIsPressFeedback;
505 | }
506 |
507 | public void setPressFeedback(boolean pressFeedback) {
508 | mIsPressFeedback = pressFeedback;
509 | if (mFitTagView != null) {
510 | mFitTagView.setPressFeedback(mIsPressFeedback);
511 | }
512 | }
513 |
514 | public TagView.OnTagClickListener getTagClickListener() {
515 | return mTagClickListener;
516 | }
517 |
518 | public void setTagClickListener(TagView.OnTagClickListener tagClickListener) {
519 | mTagClickListener = tagClickListener;
520 | // 避免先调用设置TagView,后设置监听器导致前面设置的TagView不能响应点击
521 | for (TagView tagView : mTagViews) {
522 | tagView.setTagClickListener(mTagClickListener);
523 | }
524 | }
525 |
526 | public TagView.OnTagLongClickListener getTagLongClickListener() {
527 | return mTagLongClickListener;
528 | }
529 |
530 | public void setTagLongClickListener(TagView.OnTagLongClickListener tagLongClickListener) {
531 | mTagLongClickListener = tagLongClickListener;
532 | for (TagView tagView : mTagViews) {
533 | tagView.setTagLongClickListener(mTagLongClickListener);
534 | }
535 | }
536 |
537 | public TagView.OnTagCheckListener getTagCheckListener() {
538 | return mTagCheckListener;
539 | }
540 |
541 | public void setTagCheckListener(TagView.OnTagCheckListener tagCheckListener) {
542 | mTagCheckListener = tagCheckListener;
543 | }
544 |
545 | public void setTagShape(@TagView.TagShape int tagShape) {
546 | mTagShape = tagShape;
547 | }
548 |
549 | public void setEnableRandomColor(boolean enableRandomColor) {
550 | mEnableRandomColor = enableRandomColor;
551 | }
552 |
553 | public void setIconPadding(int padding) {
554 | mIconPadding = padding;
555 | if (mFitTagView != null) {
556 | mFitTagView.setIconPadding(mIconPadding);
557 | }
558 | }
559 |
560 | /**
561 | * ==================================== 添加/删除TagView ====================================
562 | */
563 |
564 | /**
565 | * add Tag
566 | *
567 | * @param text tag content
568 | */
569 | public void addTag(String text) {
570 | if (mTagMode == TagView.MODE_CHANGE || (mTagMode == TagView.MODE_EDIT && mFitTagEditView != null)) {
571 | addView(_initTagView(text, TagView.MODE_NORMAL), getChildCount() - 1);
572 | } else {
573 | addView(_initTagView(text, mTagMode));
574 | }
575 | }
576 |
577 | /**
578 | * add Tag
579 | *
580 | * @param text tag content
581 | */
582 | public void addTagWithIcon(String text, int iconResId) {
583 | TagView tagView;
584 | if (mTagMode == TagView.MODE_CHANGE || (mTagMode == TagView.MODE_EDIT && mFitTagEditView != null)) {
585 | tagView = _initTagView(text, TagView.MODE_NORMAL);
586 | } else {
587 | tagView = _initTagView(text, mTagMode);
588 | }
589 | tagView.setDecorateIcon(ContextCompat.getDrawable(getContext(), iconResId));
590 | tagView.setIconPadding(mIconPadding);
591 | if (mTagMode == TagView.MODE_CHANGE || (mTagMode == TagView.MODE_EDIT && mFitTagEditView != null)) {
592 | addView(tagView, getChildCount() - 1);
593 | } else {
594 | addView(tagView);
595 | }
596 | }
597 |
598 |
599 | /**
600 | * 设置对应位置Tag
601 | *
602 | * @param startPos
603 | */
604 | private void _refreshPositionTag(int startPos) {
605 | for (int i = startPos; i < mTagViews.size(); i++) {
606 | mTagViews.get(i).setTag(i);
607 | }
608 | }
609 |
610 | /**
611 | * delete tag
612 | *
613 | * @param position
614 | */
615 | public void deleteTag(int position) {
616 | if (position < 0 || position >= getChildCount()) {
617 | // Toast.makeText(getContext(), "Invalid position", Toast.LENGTH_SHORT).show();
618 | return;
619 | }
620 | int pos = mTagMode == TagView.MODE_CHANGE ? position + 1 : position;
621 | if (mTagMode == TagView.MODE_CHANGE || (mTagMode == TagView.MODE_EDIT && mFitTagEditView != null)) {
622 | if (position == getChildCount() - 1) {
623 | // 最后一项为固定项不删除
624 | return;
625 | }
626 | // 第0项为固定项
627 | mTagViews.remove(pos);
628 | } else {
629 | mTagViews.remove(position);
630 | }
631 | removeViewAt(position);
632 | _refreshPositionTag(position);
633 | }
634 |
635 | /**
636 | * add Tags
637 | *
638 | * @param textList tag list
639 | */
640 | public void addTags(String... textList) {
641 | for (String text : textList) {
642 | addTag(text);
643 | }
644 | }
645 |
646 | /**
647 | * add Tags
648 | *
649 | * @param textList tag list
650 | */
651 | public void addTags(List textList) {
652 | for (String text : textList) {
653 | addTag(text);
654 | }
655 | }
656 |
657 | /**
658 | * clean Tags
659 | */
660 | public void cleanTags() {
661 | if (mTagMode == TagView.MODE_CHANGE || (mTagMode == TagView.MODE_EDIT && mFitTagEditView != null)) {
662 | removeViews(0, getChildCount() - 1);
663 | mTagViews.clear();
664 | mCheckSparseArray.clear();
665 | mTagViews.add(mFitTagView);
666 | } else {
667 | removeAllViews();
668 | mTagViews.clear();
669 | }
670 | postInvalidate();
671 | }
672 |
673 | /**
674 | * set Tags
675 | *
676 | * @param textList tag list
677 | */
678 | public void setTags(String... textList) {
679 | cleanTags();
680 | addTags(textList);
681 | }
682 |
683 | /**
684 | * set Tags
685 | *
686 | * @param textList tag list
687 | */
688 | public void setTags(List textList) {
689 | cleanTags();
690 | addTags(textList);
691 | }
692 |
693 | /**
694 | * update Tags
695 | *
696 | * @param textList tag list
697 | */
698 | public void updateTags(String... textList) {
699 | int startPos = 0;
700 | int minSize;
701 | if (mTagMode == TagView.MODE_CHANGE || (mTagMode == TagView.MODE_EDIT && mFitTagEditView != null)) {
702 | startPos = 1;
703 | minSize = Math.min(textList.length, mTagViews.size() - 1);
704 | } else {
705 | minSize = Math.min(textList.length, mTagViews.size());
706 | }
707 | for (int i = 0; i < minSize; i++) {
708 | mTagViews.get(i + startPos).setText(textList[i]);
709 | }
710 | if (mEnableRandomColor) {
711 | startPos = 0;
712 | if (mTagMode == TagView.MODE_EDIT) {
713 | startPos = 1;
714 | }
715 | for (int i = startPos; i < mTagViews.size(); i++) {
716 | _setTagRandomColors(mTagViews.get(i));
717 | }
718 | postInvalidate();
719 | }
720 | }
721 |
722 | /**
723 | * update Tags
724 | *
725 | * @param textList tag list
726 | */
727 | public void updateTags(List textList) {
728 | updateTags((String[]) textList.toArray());
729 | }
730 |
731 | /**
732 | * get checked tags
733 | *
734 | * @return
735 | */
736 | public List getCheckedTags() {
737 | List checkTags = new ArrayList<>();
738 | for (int i = 0; i < mCheckSparseArray.size(); i++) {
739 | if (mCheckSparseArray.valueAt(i)) {
740 | checkTags.add(mTagViews.get(mCheckSparseArray.keyAt(i)).getText());
741 | }
742 | }
743 | return checkTags;
744 | }
745 |
746 | /**
747 | * set tag to be checked
748 | *
749 | * @param text
750 | */
751 | public void setCheckTag(String text) {
752 | if (mTagMode == TagView.MODE_SINGLE_CHOICE) {
753 | for (TagView tagView : mTagViews) {
754 | if (tagView.getText().equals(text)) {
755 | tagView.setChecked(true);
756 | }
757 | }
758 | }
759 | }
760 |
761 | public void setCheckTag(int... indexs) {
762 | if (mTagMode == TagView.MODE_SINGLE_CHOICE) {
763 | for (int i : indexs) {
764 | if (mTagViews.get(i) != null) {
765 | mTagViews.get(i).setChecked(true);
766 | }
767 | }
768 | }
769 | }
770 |
771 | /**
772 | * delete checked tags
773 | */
774 | public void deleteCheckedTags() {
775 | for (int i = mCheckSparseArray.size() - 1; i >= 0; i--) {
776 | if (mCheckSparseArray.valueAt(i)) {
777 | deleteTag(mCheckSparseArray.keyAt(i));
778 | mCheckSparseArray.delete(mCheckSparseArray.keyAt(i));
779 | }
780 | }
781 | }
782 |
783 | /**
784 | * exit edit mode
785 | */
786 | public void exitEditMode() {
787 | if (mTagMode == TagView.MODE_EDIT && mFitTagEditView != null) {
788 | mFitTagEditView.exitEditMode();
789 | removeViewAt(getChildCount() - 1);
790 | mFitTagEditView = null;
791 | }
792 | }
793 |
794 | /**
795 | * entry edit mode
796 | */
797 | public void entryEditMode() {
798 | if (mTagMode == TagView.MODE_NORMAL || mTagMode == TagView.MODE_EDIT) {
799 | mTagMode = TagView.MODE_EDIT;
800 | _initTagEditView();
801 | addView(mFitTagEditView);
802 | }
803 | }
804 | }
805 |
--------------------------------------------------------------------------------
/taglayout/src/main/java/com/dl7/tag/TagView.java:
--------------------------------------------------------------------------------
1 | package com.dl7.tag;
2 |
3 | import android.content.Context;
4 | import android.content.res.TypedArray;
5 | import android.graphics.Bitmap;
6 | import android.graphics.BitmapFactory;
7 | import android.graphics.Canvas;
8 | import android.graphics.Color;
9 | import android.graphics.Paint;
10 | import android.graphics.PorterDuff;
11 | import android.graphics.RectF;
12 | import android.graphics.drawable.Animatable;
13 | import android.graphics.drawable.Drawable;
14 | import android.os.Parcel;
15 | import android.os.Parcelable;
16 | import android.support.annotation.IntDef;
17 | import android.support.v4.view.MotionEventCompat;
18 | import android.text.TextUtils;
19 | import android.util.AttributeSet;
20 | import android.view.Gravity;
21 | import android.view.MotionEvent;
22 | import android.view.View;
23 | import android.view.ViewParent;
24 |
25 | import com.dl7.tag.drawable.RotateDrawable;
26 | import com.dl7.tag.utils.MeasureUtils;
27 |
28 | import java.lang.annotation.ElementType;
29 | import java.lang.annotation.Retention;
30 | import java.lang.annotation.RetentionPolicy;
31 | import java.lang.annotation.Target;
32 |
33 |
34 | /**
35 | * Created by long on 2016/7/20.
36 | * TagView
37 | */
38 | public class TagView extends View {
39 |
40 | // 无效数值
41 | public final static int INVALID_VALUE = -1;
42 |
43 | // 3种外形模式:圆角矩形、圆弧、直角矩形
44 | public final static int SHAPE_ROUND_RECT = 101;
45 | public final static int SHAPE_ARC = 102;
46 | public final static int SHAPE_RECT = 103;
47 |
48 | @IntDef({SHAPE_ROUND_RECT, SHAPE_ARC, SHAPE_RECT})
49 | @Retention(RetentionPolicy.SOURCE)
50 | @Target(ElementType.PARAMETER)
51 | public @interface TagShape {
52 | }
53 |
54 | // 类型模式:正常、编辑、换一换、单选、多选、图标选中消失、图标选中切换
55 | public final static int MODE_NORMAL = 201;
56 | public final static int MODE_EDIT = 202;
57 | public final static int MODE_CHANGE = 203;
58 | public final static int MODE_SINGLE_CHOICE = 204;
59 | public final static int MODE_MULTI_CHOICE = 205;
60 | public final static int MODE_ICON_CHECK_INVISIBLE = 206;
61 | public final static int MODE_ICON_CHECK_CHANGE = 207;
62 |
63 | @IntDef({MODE_NORMAL, MODE_EDIT, MODE_CHANGE, MODE_SINGLE_CHOICE, MODE_MULTI_CHOICE, MODE_ICON_CHECK_INVISIBLE, MODE_ICON_CHECK_CHANGE})
64 | @Retention(RetentionPolicy.SOURCE)
65 | @Target(ElementType.PARAMETER)
66 | public @interface TagMode {
67 | }
68 |
69 | // 显示外形
70 | private int mTagShape = SHAPE_ROUND_RECT;
71 | // 显示类型
72 | private int mTagMode = MODE_NORMAL;
73 | // 画笔
74 | private Paint mPaint;
75 | // 背景色
76 | private int mBgColor = Color.WHITE;
77 | // 边框颜色
78 | private int mBorderColor = Color.parseColor("#ff333333");
79 | // 原始标签颜色
80 | private int mTextColor = Color.parseColor("#ff666666");
81 | // 选中状态背景色
82 | private int mBgColorChecked = Color.WHITE;
83 | // 选中状态边框颜色
84 | private int mBorderColorChecked = Color.parseColor("#ff333333");
85 | // 选中状态字体颜色
86 | private int mTextColorChecked = Color.parseColor("#ff666666");
87 | // 遮罩颜色
88 | private int mScrimColor = Color.argb(0x66, 0xc0, 0xc0, 0xc0);
89 | // 字体大小
90 | private float mTextSize;
91 | // 字体宽度和高度
92 | private int mFontLen;
93 | private int mFontH;
94 | private int mFontLenChecked;
95 | // 基线偏移距离
96 | private float mBaseLineDistance;
97 | // 边框大小
98 | private float mBorderWidth;
99 | // 边框角半径
100 | private float mRadius;
101 | // 内容
102 | private String mText;
103 | // 选中时内容
104 | private String mTextChecked;
105 | // 显示的文字
106 | private String mShowText;
107 | // 字体水平空隙
108 | private int mHorizontalPadding;
109 | // 字体垂直空隙
110 | private int mVerticalPadding;
111 | // 边框矩形
112 | private RectF mRect;
113 | // 装饰的icon
114 | private Drawable mDecorateIcon;
115 | // 变化模式下的icon
116 | private Drawable mDecorateIconChange;
117 | // 设置图标的位置,只支持左右两边
118 | private int mIconGravity = Gravity.LEFT;
119 | // icon和文字间距
120 | private int mIconPadding = 0;
121 | // icon大小
122 | private int mIconSize = 0;
123 | // 是否选中
124 | private boolean mIsChecked = false;
125 | // 是否自动切换选中状态,不使能可以灵活地选择切换,通常用于等待网络返回再做切换
126 | private boolean mIsAutoToggleCheck = false;
127 | // 是否被按住
128 | private boolean mIsPressed = false;
129 | // 是否使能按压反馈
130 | private boolean mIsPressFeedback = false;
131 | // 点击监听器
132 | private OnTagClickListener mTagClickListener;
133 | private OnTagLongClickListener mTagLongClickListener;
134 | private OnTagCheckListener mTagCheckListener;
135 |
136 |
137 | public TagView(Context context) {
138 | super(context);
139 | _init(context, null);
140 | }
141 |
142 | public TagView(Context context, String text) {
143 | this(context);
144 | mText = text;
145 | }
146 |
147 | public TagView(Context context, AttributeSet attrs) {
148 | super(context, attrs);
149 | _init(context, attrs);
150 | }
151 |
152 | private void _init(Context context, AttributeSet attrs) {
153 | mBorderWidth = MeasureUtils.dp2px(context, 0.5f);
154 | mRadius = MeasureUtils.dp2px(context, 5f);
155 | mHorizontalPadding = (int) MeasureUtils.dp2px(context, 5f);
156 | mVerticalPadding = (int) MeasureUtils.dp2px(context, 5f);
157 | mIconPadding = (int) MeasureUtils.dp2px(context, 3f);
158 | mTextSize = MeasureUtils.dp2px(context, 14f);
159 |
160 | if (attrs != null) {
161 | final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.TagView);
162 | try {
163 | mTagShape = a.getInteger(R.styleable.TagView_tag_shape, TagView.SHAPE_ROUND_RECT);
164 | mTagMode = a.getInteger(R.styleable.TagView_tag_mode, MODE_NORMAL);
165 | if (mTagMode == MODE_SINGLE_CHOICE || mTagMode == MODE_ICON_CHECK_INVISIBLE || mTagMode == MODE_ICON_CHECK_CHANGE) {
166 | mIsAutoToggleCheck = true;
167 | mIsChecked = a.getBoolean(R.styleable.TagView_tag_checked, false);
168 | mDecorateIconChange = a.getDrawable(R.styleable.TagView_tag_icon_change);
169 | }
170 | mIsAutoToggleCheck = a.getBoolean(R.styleable.TagView_tag_auto_check, mIsAutoToggleCheck);
171 | mIsPressFeedback = a.getBoolean(R.styleable.TagView_tag_press_feedback, mIsPressFeedback);
172 |
173 | mText = a.getString(R.styleable.TagView_tag_text);
174 | mTextChecked = a.getString(R.styleable.TagView_tag_text_check);
175 | mTextSize = a.getDimension(R.styleable.TagView_tag_text_size, mTextSize);
176 | mBgColor = a.getColor(R.styleable.TagView_tag_bg_color, Color.WHITE);
177 | mBorderColor = a.getColor(R.styleable.TagView_tag_border_color, Color.parseColor("#ff333333"));
178 | mTextColor = a.getColor(R.styleable.TagView_tag_text_color, Color.parseColor("#ff666666"));
179 | mBgColorChecked = a.getColor(R.styleable.TagView_tag_bg_color_check, mBgColor);
180 | mBorderColorChecked = a.getColor(R.styleable.TagView_tag_border_color_check, mBorderColor);
181 | mTextColorChecked = a.getColor(R.styleable.TagView_tag_text_color_check, mTextColor);
182 | mBorderWidth = a.getDimension(R.styleable.TagView_tag_border_width, mBorderWidth);
183 | mRadius = a.getDimension(R.styleable.TagView_tag_border_radius, mRadius);
184 | mHorizontalPadding = (int) a.getDimension(R.styleable.TagView_tag_horizontal_padding, mHorizontalPadding);
185 | mVerticalPadding = (int) a.getDimension(R.styleable.TagView_tag_vertical_padding, mVerticalPadding);
186 | mIconPadding = (int) a.getDimension(R.styleable.TagView_tag_icon_padding, mIconPadding);
187 | mDecorateIcon = a.getDrawable(R.styleable.TagView_tag_icon);
188 | mIconGravity = a.getInteger(R.styleable.TagView_tag_gravity, Gravity.LEFT);
189 | } finally {
190 | a.recycle();
191 | }
192 | }
193 |
194 | if (mTagMode == MODE_ICON_CHECK_CHANGE && mDecorateIconChange == null) {
195 | throw new RuntimeException("You must set the drawable by 'tag_icon_change' property in MODE_ICON_CHECK_CHANGE mode");
196 | }
197 | if (mDecorateIcon != null) {
198 | mDecorateIcon.setCallback(this);
199 | }
200 | if (mDecorateIconChange != null) {
201 | mDecorateIconChange.setColorFilter(mTextColorChecked, PorterDuff.Mode.SRC_IN);
202 | mDecorateIconChange.setCallback(this);
203 | }
204 | mRect = new RectF();
205 | mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
206 | setClickable(true);
207 | if (!isSaveEnabled()) {
208 | // 使能状态保存
209 | setSaveEnabled(true);
210 | }
211 |
212 | setOnClickListener(new OnClickListener() {
213 | @Override
214 | public void onClick(View v) {
215 | if (mTagClickListener != null) {
216 | mTagClickListener.onTagClick(getTag() == null ? 0 : (int) getTag(), mText, mTagMode);
217 | }
218 | }
219 | });
220 | setOnLongClickListener(new OnLongClickListener() {
221 | @Override
222 | public boolean onLongClick(View v) {
223 | if (mTagLongClickListener != null) {
224 | mTagLongClickListener.onTagLongClick(getTag() == null ? 0 : (int) getTag(), mText, mTagMode);
225 | }
226 | return true;
227 | }
228 | });
229 | }
230 |
231 | /**
232 | * 调整显示的字符
233 | * @param maxWidth
234 | * @return
235 | */
236 | private int _adjustText(int maxWidth) {
237 | if (mPaint.getTextSize() != mTextSize) {
238 | mPaint.setTextSize(mTextSize);
239 | final Paint.FontMetrics fontMetrics = mPaint.getFontMetrics();
240 | // 文字高度
241 | mFontH = (int) (fontMetrics.descent - fontMetrics.ascent);
242 | // 用来设置基线的偏移量,再加上 getHeight() / 2 就是基线坐标
243 | mBaseLineDistance = (int) Math.ceil((fontMetrics.descent - fontMetrics.ascent) / 2 - fontMetrics.descent);
244 | }
245 | // 计算文字宽度
246 | if (TextUtils.isEmpty(mText)) {
247 | mText = "";
248 | }
249 | mFontLen = (int) mPaint.measureText(mText);
250 | if (TextUtils.isEmpty(mTextChecked)) {
251 | mFontLenChecked = mFontLen;
252 | } else {
253 | mFontLenChecked = (int) mPaint.measureText(mTextChecked);
254 | }
255 | // 计算图标大小
256 | if ((mDecorateIcon != null || mDecorateIconChange != null) && mIconSize != mFontH) {
257 | mIconSize = mFontH;
258 | }
259 | // 计算出了文字外所需要占用的宽度
260 | int allPadding;
261 | if (mTagMode == MODE_ICON_CHECK_CHANGE && mIsChecked) {
262 | allPadding = mIconPadding + mIconSize + mHorizontalPadding * 2;
263 | } else if (mDecorateIcon != null) {
264 | allPadding = (mTagMode == MODE_ICON_CHECK_INVISIBLE && mIsChecked) ? mHorizontalPadding * 2 :
265 | mIconPadding + mIconSize + mHorizontalPadding * 2;
266 | } else {
267 | allPadding = mHorizontalPadding * 2;
268 | }
269 | // 设置显示的文字
270 | String showText = (mIsChecked && !TextUtils.isEmpty(mTextChecked)) ? mTextChecked : mText;
271 | if (mIsChecked && mFontLenChecked + allPadding > maxWidth) {
272 | float pointWidth = mPaint.measureText(".");
273 | // 计算能显示的字体长度
274 | float maxTextWidth = maxWidth - allPadding - pointWidth * 3;
275 | mShowText = _clipShowText(showText, mPaint, maxTextWidth);
276 | mFontLenChecked = (int) mPaint.measureText(mShowText);
277 | } else if (!mIsChecked && mFontLen + allPadding > maxWidth) {
278 | float pointWidth = mPaint.measureText(".");
279 | // 计算能显示的字体长度
280 | float maxTextWidth = maxWidth - allPadding - pointWidth * 3;
281 | mShowText = _clipShowText(showText, mPaint, maxTextWidth);
282 | mFontLen = (int) mPaint.measureText(mShowText);
283 | } else {
284 | mShowText = showText;
285 | }
286 |
287 | return allPadding;
288 | }
289 |
290 | @Override
291 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
292 | ViewParent parent = getParent();
293 | if (parent instanceof TagLayout) {
294 | int fitTagNum = ((TagLayout) getParent()).getFitTagNum();
295 | if (fitTagNum != INVALID_VALUE) {
296 | int availableWidth = ((TagLayout) getParent()).getAvailableWidth();
297 | int horizontalInterval = ((TagLayout) getParent()).getHorizontalInterval();
298 | int fitWidth = (availableWidth - (fitTagNum - 1) * horizontalInterval) / fitTagNum;
299 | widthMeasureSpec = MeasureSpec.makeMeasureSpec(fitWidth, MeasureSpec.EXACTLY);
300 | }
301 | }
302 |
303 | int allPadding = _adjustText(MeasureSpec.getSize(widthMeasureSpec));
304 | int fontLen = mIsChecked ? mFontLenChecked : mFontLen;
305 | // 如果为精确测量 MeasureSpec.EXACTLY,则直接使用测量的大小,否则让控件实现自适应
306 | // 如果你用了精确测量则 mHorizontalPadding 和 mVerticalPadding 会对最终大小判定无效
307 | int width = (MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY) ?
308 | MeasureSpec.getSize(widthMeasureSpec) : allPadding + fontLen;
309 | int height = (MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY) ?
310 | MeasureSpec.getSize(heightMeasureSpec) : mVerticalPadding * 2 + mFontH;
311 | setMeasuredDimension(width, height);
312 | // 计算图标放置位置
313 | if (mDecorateIcon != null || mDecorateIconChange != null) {
314 | int top = (height - mIconSize) / 2;
315 | int left;
316 | if (mIconGravity == Gravity.RIGHT) {
317 | int padding = (width - mIconSize - fontLen - mIconPadding) / 2;
318 | left = width - padding - mIconSize;
319 | } else {
320 | left = (width - mIconSize - fontLen - mIconPadding) / 2;
321 | }
322 | if (mTagMode == MODE_ICON_CHECK_CHANGE && mIsChecked && mDecorateIconChange != null) {
323 | mDecorateIconChange.setBounds(left, top, mIconSize + left, mIconSize + top);
324 | } else if (mDecorateIcon != null) {
325 | mDecorateIcon.setBounds(left, top, mIconSize + left, mIconSize + top);
326 | }
327 | }
328 | }
329 |
330 | @Override
331 | protected void onSizeChanged(int w, int h, int oldw, int oldh) {
332 | super.onSizeChanged(w, h, oldw, oldh);
333 | // 设置矩形边框
334 | mRect.set(mBorderWidth, mBorderWidth, w - mBorderWidth, h - mBorderWidth);
335 | }
336 |
337 | @Override
338 | protected void onDraw(Canvas canvas) {
339 | // 圆角
340 | float radius = mRadius;
341 | if (mTagShape == SHAPE_ARC) {
342 | radius = mRect.height() / 2;
343 | } else if (mTagShape == SHAPE_RECT) {
344 | radius = 0;
345 | }
346 | // 判断按压反馈和选中状态
347 | final boolean isChecked = (mIsPressed && mIsPressFeedback) || mIsChecked;
348 | // 绘制背景
349 | mPaint.setStyle(Paint.Style.FILL);
350 | if (isChecked) {
351 | mPaint.setColor(mBgColorChecked);
352 | } else {
353 | mPaint.setColor(mBgColor);
354 | }
355 | canvas.drawRoundRect(mRect, radius, radius, mPaint);
356 | // 绘制边框
357 | mPaint.setStyle(Paint.Style.STROKE);
358 | mPaint.setStrokeWidth(mBorderWidth);
359 | if (isChecked) {
360 | mPaint.setColor(mBorderColorChecked);
361 | } else {
362 | mPaint.setColor(mBorderColor);
363 | }
364 | canvas.drawRoundRect(mRect, radius, radius, mPaint);
365 | // 绘制文字
366 | mPaint.setStyle(Paint.Style.FILL);
367 | if (isChecked) {
368 | mPaint.setColor(mTextColorChecked);
369 | int padding = (mTagMode == MODE_ICON_CHECK_INVISIBLE && mIsChecked) ? 0 : mIconSize + mIconPadding;
370 | int fontLen = mIsChecked ? mFontLenChecked : mFontLen;
371 | canvas.drawText(mShowText,
372 | mIconGravity == Gravity.RIGHT ? (getWidth() - fontLen - padding) / 2
373 | : (getWidth() - fontLen - padding) / 2 + padding,
374 | getHeight() / 2 + mBaseLineDistance, mPaint);
375 | } else {
376 | mPaint.setColor(mTextColor);
377 | int padding = mDecorateIcon == null ? 0 : mIconSize + mIconPadding;
378 | canvas.drawText(mShowText,
379 | mIconGravity == Gravity.RIGHT ? (getWidth() - mFontLen - padding) / 2
380 | : (getWidth() - mFontLen - padding) / 2 + padding,
381 | getHeight() / 2 + mBaseLineDistance, mPaint);
382 | }
383 | // 绘制Icon
384 | if (mTagMode == MODE_ICON_CHECK_CHANGE && mIsChecked && mDecorateIconChange != null) {
385 | mDecorateIconChange.draw(canvas);
386 | } else if (mTagMode == MODE_ICON_CHECK_INVISIBLE && mIsChecked) {
387 | // Don't need to draw
388 | } else if (mDecorateIcon != null) {
389 | mDecorateIcon.setColorFilter(mPaint.getColor(), PorterDuff.Mode.SRC_IN);
390 | mDecorateIcon.draw(canvas);
391 | }
392 | // 绘制半透明遮罩
393 | if (mIsPressed && (mIsChecked || !mIsPressFeedback)) {
394 | mPaint.setColor(mScrimColor);
395 | canvas.drawRoundRect(mRect, radius, radius, mPaint);
396 | }
397 | }
398 |
399 | @Override
400 | protected void onDetachedFromWindow() {
401 | if (mDecorateIcon != null && mDecorateIcon instanceof Animatable) {
402 | ((Animatable)mDecorateIcon).stop();
403 | mDecorateIcon.setCallback(null);
404 | }
405 | if (mDecorateIconChange != null && mDecorateIconChange instanceof Animatable) {
406 | ((Animatable)mDecorateIconChange).stop();
407 | mDecorateIconChange.setCallback(null);
408 | }
409 | super.onDetachedFromWindow();
410 | }
411 |
412 | @Override
413 | public void invalidateDrawable(Drawable drawable) {
414 | if ((mDecorateIcon != null && mDecorateIcon instanceof Animatable) ||
415 | (mDecorateIconChange != null && mDecorateIconChange instanceof Animatable)) {
416 | postInvalidate();
417 | } else {
418 | super.invalidateDrawable(drawable);
419 | }
420 | }
421 |
422 |
423 | /**
424 | * ==================================== 触摸点击控制 ====================================
425 | */
426 |
427 | @Override
428 | public boolean onTouchEvent(MotionEvent event) {
429 | switch (MotionEventCompat.getActionMasked(event)) {
430 | case MotionEvent.ACTION_DOWN:
431 | mIsPressed = true;
432 | invalidate();
433 | break;
434 | case MotionEvent.ACTION_MOVE:
435 | if (mIsPressed && !_isViewUnder(event.getX(), event.getY())) {
436 | mIsPressed = false;
437 | invalidate();
438 | }
439 | break;
440 | case MotionEvent.ACTION_UP:
441 | if (_isViewUnder(event.getX(), event.getY())) {
442 | _toggleTagCheckStatus();
443 | }
444 | case MotionEvent.ACTION_CANCEL:
445 | if (mIsPressed) {
446 | mIsPressed = false;
447 | invalidate();
448 | }
449 | break;
450 | }
451 | return super.onTouchEvent(event);
452 | }
453 |
454 | /**
455 | * 判断是否在 Tag 控件内
456 | *
457 | * @param x
458 | * @param y
459 | * @return
460 | */
461 | private boolean _isViewUnder(float x, float y) {
462 | return x >= 0 && x < getWidth() &&
463 | y >= 0 && y < getHeight();
464 | }
465 |
466 | /**
467 | * 切换tag选中状态
468 | */
469 | private void _toggleTagCheckStatus() {
470 | if (mIsAutoToggleCheck) {
471 | setChecked(!mIsChecked);
472 | }
473 | }
474 |
475 | public boolean isChecked() {
476 | return mIsChecked;
477 | }
478 |
479 | /**
480 | * 设置选中状态
481 | *
482 | * @param checked
483 | */
484 | public void setChecked(boolean checked) {
485 | _setTagCheckStatus(checked);
486 | if (mTagCheckListener != null) {
487 | mTagCheckListener.onTagCheck(getTag() == null ? 0 : (int) getTag(), mText, mIsChecked);
488 | }
489 | }
490 |
491 | /**
492 | * 清除选中状态
493 | */
494 | public void cleanTagCheckStatus() {
495 | _setTagCheckStatus(false);
496 | }
497 |
498 | private void _setTagCheckStatus(boolean isChecked) {
499 | if (mIsChecked == isChecked) {
500 | return;
501 | }
502 | mIsChecked = isChecked;
503 | // 注意,这里用 requestLayout() 而不是只用 invalidate(),因为 invalidate() 没法回调 onMeasure() 进行测量,
504 | // 如果控件自适应大小的话是有可能改变大小的,所以加上 requestLayout()
505 | // requestLayout();
506 | // invalidate();
507 | updateView();
508 | }
509 |
510 | /**
511 | * 裁剪Text
512 | *
513 | * @param oriText
514 | * @param paint
515 | * @param maxTextWidth
516 | * @return
517 | */
518 | private String _clipShowText(String oriText, Paint paint, float maxTextWidth) {
519 | float tmpWidth = 0;
520 | StringBuilder strBuilder = new StringBuilder();
521 | for (int i = 0; i < oriText.length(); i++) {
522 | char c = oriText.charAt(i);
523 | float cWidth = paint.measureText(String.valueOf(c));
524 | // 计算每个字符的宽度之和,如果超过能显示的长度则退出
525 | if (tmpWidth + cWidth > maxTextWidth) {
526 | break;
527 | }
528 | strBuilder.append(c);
529 | tmpWidth += cWidth;
530 | }
531 | // 末尾添加3个.并设置为显示字符
532 | strBuilder.append("...");
533 | return strBuilder.toString();
534 | }
535 |
536 | /**
537 | * ==================================== 接口 ====================================
538 | * 带有Lazy的后缀统一不调用 requestLayout() 和 invalidate(),避免多次绘制
539 | */
540 |
541 | public int getTagShape() {
542 | return mTagShape;
543 | }
544 |
545 | public void setTagShape(int tagShape) {
546 | mTagShape = tagShape;
547 | updateView();
548 | }
549 |
550 | public void setTagShapeLazy(int tagShape) {
551 | mTagShape = tagShape;
552 | }
553 |
554 | public int getTagMode() {
555 | return mTagMode;
556 | }
557 |
558 | public void setTagMode(int tagMode) {
559 | mTagMode = tagMode;
560 | if (mTagMode == MODE_CHANGE) {
561 | Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.ic_change);
562 | mDecorateIcon = new RotateDrawable(bitmap);
563 | mDecorateIcon.setCallback(this);
564 | }
565 | updateView();
566 | }
567 |
568 | public void setTagModeLazy(int tagMode) {
569 | mTagMode = tagMode;
570 | if (mTagMode == MODE_SINGLE_CHOICE || mTagMode == MODE_MULTI_CHOICE) {
571 | setPressFeedback(true);
572 | mIsAutoToggleCheck = true;
573 | } else if (mTagMode == MODE_CHANGE) {
574 | Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.ic_change);
575 | mDecorateIcon = new RotateDrawable(bitmap);
576 | mDecorateIcon.setCallback(this);
577 | }
578 | }
579 |
580 | public int getBgColor() {
581 | return mBgColor;
582 | }
583 |
584 | public void setBgColor(int bgColor) {
585 | mBgColor = bgColor;
586 | // 设置颜色调用这个就行,回调onDraw()
587 | invalidate();
588 | }
589 |
590 | public void setBgColorLazy(int bgColor) {
591 | mBgColor = bgColor;
592 | }
593 |
594 | public int getBorderColor() {
595 | return mBorderColor;
596 | }
597 |
598 | public void setBorderColor(int borderColor) {
599 | mBorderColor = borderColor;
600 | invalidate();
601 | }
602 |
603 | public void setBorderColorLazy(int borderColor) {
604 | mBorderColor = borderColor;
605 | }
606 |
607 | public int getTextColor() {
608 | return mTextColor;
609 | }
610 |
611 | public void setTextColor(int textColor) {
612 | mTextColor = textColor;
613 | invalidate();
614 | }
615 |
616 | public void setTextColorLazy(int textColor) {
617 | mTextColor = textColor;
618 | }
619 |
620 | public int getBgColorChecked() {
621 | return mBgColorChecked;
622 | }
623 |
624 | public void setBgColorChecked(int bgColorChecked) {
625 | mBgColorChecked = bgColorChecked;
626 | invalidate();
627 | }
628 |
629 | public void setBgColorCheckedLazy(int bgColorChecked) {
630 | mBgColorChecked = bgColorChecked;
631 | }
632 |
633 | public int getBorderColorChecked() {
634 | return mBorderColorChecked;
635 | }
636 |
637 | public void setBorderColorChecked(int borderColorChecked) {
638 | mBorderColorChecked = borderColorChecked;
639 | invalidate();
640 | }
641 |
642 | public void setBorderColorCheckedLazy(int borderColorChecked) {
643 | mBorderColorChecked = borderColorChecked;
644 | }
645 |
646 | public int getTextColorChecked() {
647 | return mTextColorChecked;
648 | }
649 |
650 | public void setTextColorChecked(int textColorChecked) {
651 | mTextColorChecked = textColorChecked;
652 | if (mDecorateIconChange != null) {
653 | mDecorateIconChange.setColorFilter(mTextColorChecked, PorterDuff.Mode.SRC_IN);
654 | }
655 | invalidate();
656 | }
657 |
658 | public void setTextColorCheckedLazy(int textColorChecked) {
659 | mTextColorChecked = textColorChecked;
660 | if (mDecorateIconChange != null) {
661 | mDecorateIconChange.setColorFilter(mTextColorChecked, PorterDuff.Mode.SRC_IN);
662 | }
663 | }
664 |
665 | public int getScrimColor() {
666 | return mScrimColor;
667 | }
668 |
669 | public void setScrimColor(int scrimColor) {
670 | mScrimColor = scrimColor;
671 | invalidate();
672 | }
673 |
674 | public void setScrimColorLazy(int scrimColor) {
675 | mScrimColor = scrimColor;
676 | }
677 |
678 | public float getTextSize() {
679 | return mTextSize;
680 | }
681 |
682 | public void setTextSize(float textSize) {
683 | mTextSize = textSize;
684 | updateView();
685 | }
686 |
687 | public void setTextSizeLazy(float textSize) {
688 | mTextSize = textSize;
689 | }
690 |
691 | public float getBorderWidth() {
692 | return mBorderWidth;
693 | }
694 |
695 | public void setBorderWidth(float borderWidth) {
696 | mBorderWidth = borderWidth;
697 | invalidate();
698 | }
699 |
700 | public void setBorderWidthLazy(float borderWidth) {
701 | mBorderWidth = borderWidth;
702 | }
703 |
704 | public float getRadius() {
705 | return mRadius;
706 | }
707 |
708 | public void setRadius(float radius) {
709 | mRadius = radius;
710 | invalidate();
711 | }
712 |
713 | public void setRadiusLazy(float radius) {
714 | mRadius = radius;
715 | }
716 |
717 | public String getText() {
718 | return mText;
719 | }
720 |
721 | public void setText(String text) {
722 | mText = text;
723 | updateView();
724 | }
725 |
726 | public void setTextLazy(String text) {
727 | mText = text;
728 | }
729 |
730 | public String getTextChecked() {
731 | return mTextChecked;
732 | }
733 |
734 | public void setTextChecked(String textChecked) {
735 | mTextChecked = textChecked;
736 | updateView();
737 | }
738 |
739 | public void setTextCheckedLazy(String textChecked) {
740 | mTextChecked = textChecked;
741 | }
742 |
743 | public int getHorizontalPadding() {
744 | return mHorizontalPadding;
745 | }
746 |
747 | public void setHorizontalPaddingLazy(int horizontalPadding) {
748 | mHorizontalPadding = horizontalPadding;
749 | }
750 |
751 | public void setHorizontalPadding(int horizontalPadding) {
752 | mHorizontalPadding = horizontalPadding;
753 | updateView();
754 | }
755 |
756 | public int getVerticalPadding() {
757 | return mVerticalPadding;
758 | }
759 |
760 | public void setVerticalPaddingLazy(int verticalPadding) {
761 | mVerticalPadding = verticalPadding;
762 | }
763 |
764 | public void setVerticalPadding(int verticalPadding) {
765 | mVerticalPadding = verticalPadding;
766 | updateView();
767 | }
768 |
769 | public Drawable getDecorateIcon() {
770 | return mDecorateIcon;
771 | }
772 |
773 | public void setDecorateIconLazy(Drawable decorateIcon) {
774 | mDecorateIcon = decorateIcon;
775 | mDecorateIcon.setCallback(this);
776 | }
777 |
778 | public void setDecorateIcon(Drawable decorateIcon) {
779 | mDecorateIcon = decorateIcon;
780 | mDecorateIcon.setCallback(this);
781 | updateView();
782 | }
783 |
784 | public Drawable getDecorateIconChange() {
785 | return mDecorateIconChange;
786 | }
787 |
788 | public void setDecorateIconChange(Drawable decorateIconChange) {
789 | mDecorateIconChange = decorateIconChange;
790 | mDecorateIconChange.setColorFilter(mTextColorChecked, PorterDuff.Mode.SRC_IN);
791 | mDecorateIconChange.setCallback(this);
792 | updateView();
793 | }
794 |
795 | public void setDecorateIconChangeLazy(Drawable decorateIconChange) {
796 | mDecorateIconChange = decorateIconChange;
797 | mDecorateIconChange.setColorFilter(mTextColorChecked, PorterDuff.Mode.SRC_IN);
798 | mDecorateIconChange.setCallback(this);
799 | }
800 |
801 | public int getIconPadding() {
802 | return mIconPadding;
803 | }
804 |
805 | public void setIconPadding(int iconPadding) {
806 | mIconPadding = iconPadding;
807 | updateView();
808 | }
809 |
810 | public void setIconPaddingLazy(int iconPadding) {
811 | mIconPadding = iconPadding;
812 | }
813 |
814 |
815 | public boolean isAutoToggleCheck() {
816 | return mIsAutoToggleCheck;
817 | }
818 |
819 | public void setAutoToggleCheck(boolean autoToggleCheck) {
820 | mIsAutoToggleCheck = autoToggleCheck;
821 | }
822 |
823 | public boolean isPressFeedback() {
824 | return mIsPressFeedback;
825 | }
826 |
827 | public void setPressFeedback(boolean pressFeedback) {
828 | mIsPressFeedback = pressFeedback;
829 | }
830 |
831 | /**
832 | * 调用这些接口进行属性设置如果最后可能会改变按钮的大小的话最后调用一下这个接口,以刷新界面,建议属性直接在布局里设置
833 | * 只需要回调onDraw()的话调用invalidate()就可以了
834 | */
835 | public void updateView() {
836 | requestLayout();
837 | invalidate();
838 | }
839 |
840 | /**
841 | * ==================================== 点击监听 ====================================
842 | */
843 |
844 | public OnTagClickListener getTagClickListener() {
845 | return mTagClickListener;
846 | }
847 |
848 | public void setTagClickListener(OnTagClickListener tagClickListener) {
849 | mTagClickListener = tagClickListener;
850 | }
851 |
852 | public OnTagLongClickListener getTagLongClickListener() {
853 | return mTagLongClickListener;
854 | }
855 |
856 | public void setTagLongClickListener(OnTagLongClickListener tagLongClickListener) {
857 | mTagLongClickListener = tagLongClickListener;
858 | }
859 |
860 | public OnTagCheckListener getTagCheckListener() {
861 | return mTagCheckListener;
862 | }
863 |
864 | public void setTagCheckListener(OnTagCheckListener tagCheckListener) {
865 | mTagCheckListener = tagCheckListener;
866 | }
867 |
868 | /**
869 | * ==================================== 状态保存 ====================================
870 | */
871 |
872 | @Override
873 | public Parcelable onSaveInstanceState() {
874 | Parcelable superState = super.onSaveInstanceState();
875 | SavedState state = new SavedState(superState);
876 | state.isChecked = mIsChecked;
877 | return state;
878 | }
879 |
880 | @Override
881 | public void onRestoreInstanceState(Parcelable state) {
882 | if (!(state instanceof SavedState)) {
883 | super.onRestoreInstanceState(state);
884 | return;
885 | }
886 | SavedState ss = (SavedState) state;
887 | super.onRestoreInstanceState(ss.getSuperState());
888 | mIsChecked = ss.isChecked;
889 | }
890 |
891 | public static class SavedState extends BaseSavedState {
892 |
893 | boolean isChecked;
894 |
895 | public SavedState(Parcelable superState) {
896 | super(superState);
897 | }
898 |
899 | @Override
900 | public void writeToParcel(Parcel out, int flags) {
901 | super.writeToParcel(out, flags);
902 | out.writeInt(isChecked ? 1 : 0);
903 | }
904 |
905 | @SuppressWarnings("hiding")
906 | public static final Creator CREATOR
907 | = new Creator() {
908 | public SavedState createFromParcel(Parcel in) {
909 | return new SavedState(in);
910 | }
911 |
912 | public SavedState[] newArray(int size) {
913 | return new SavedState[size];
914 | }
915 | };
916 |
917 | private SavedState(Parcel in) {
918 | super(in);
919 | isChecked = in.readInt() == 1;
920 | }
921 | }
922 |
923 | /**
924 | * ==================================== 监听器 ====================================
925 | */
926 |
927 | public interface OnTagClickListener {
928 | void onTagClick(int position, String text, @TagMode int tagMode);
929 | }
930 |
931 | public interface OnTagLongClickListener {
932 | void onTagLongClick(int position, String text, @TagMode int tagMode);
933 | }
934 |
935 | public interface OnTagCheckListener {
936 | void onTagCheck(int position, String text, boolean isChecked);
937 | }
938 | }
939 |
--------------------------------------------------------------------------------