diff --git a/.gitignore b/.gitignore index fbe08b1..6c9b4c3 100755 --- a/.gitignore +++ b/.gitignore @@ -1,46 +1,46 @@ -.gradle -/local.properties -/.idea/workspace.xml -.DS_Store -/build -# built application files -*.ap_ - -# files for the dex VM -*.dex - -# Java class files -*.class -.DS_Store - -# generated files -bin/ -gen/ -Wiki/ - -# Local configuration file (sdk path, etc) -local.properties - -# Eclipse project files -.classpath -.project -.settings/ - -# Proguard folder generated by Eclipse -proguard/ - -#Android Studio -build/ -src/androidTest/ - -# Intellij project files -*.iml -*.ipr -*.iws -.idea/ - -#gradle -.gradle/ -.idea -app/build +.gradle +/local.properties +/.idea/workspace.xml +.DS_Store +/build +# built application files +*.ap_ + +# files for the dex VM +*.dex + +# Java class files +*.class +.DS_Store + +# generated files +bin/ +gen/ +Wiki/ + +# Local configuration file (sdk path, etc) +local.properties + +# Eclipse project files +.classpath +.project +.settings/ + +# Proguard folder generated by Eclipse +proguard/ + +#Android Studio +build/ +src/androidTest/ + +# Intellij project files +*.iml +*.ipr +*.iws +.idea/ + +#gradle +.gradle/ +.idea +app/build app/src/androidTest \ No newline at end of file diff --git a/.travis.yml b/.travis.yml index 836dace..a3a152b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,26 +1,32 @@ -language: android -android: - components: - # Uncomment the lines below if you want to - # use the latest revision of Android SDK Tools - # - platform-tools - # - tools - - # The BuildTools version used by your project - - build-tools-22.0.1 - - # The SDK version used to compile your project - - android-23 - - # Additional components - - extra-google-google_play_services - - extra-google-m2repository - - extra-android-m2repository - - addon-google_apis-google-19 - - # Specify at least one system image, - # if you need to run emulator(s) during your tests - - sys-img-armeabi-v7a-android-19 - - sys-img-x86-android-17 - -script: ./gradlew assembleDebug \ No newline at end of file +language: android +jdk: oraclejdk8 + +env: + global: + - ANDROID_ABI=armeabi-v7a + +android: + components: + - tools + - android-22 + - android-28 + - sys-img-armeabi-v7a-android-22 + - extra-android-m2repository + - extra-android-support + - extra + licenses: + - android-sdk-license-.+ + +before_install: + - mkdir "$ANDROID_HOME/licenses" || true + - echo "d56f5187479451eabf01fb78af6dfcb131a6481e" > "$ANDROID_HOME/licenses/android-sdk-license" + +before_script: + - chmod +x gradlew + - echo no | android create avd --force -n test -t android-22 --abi $ANDROID_ABI + - emulator -avd test -no-window & + - android-wait-for-emulator + - adb shell input keyevent 82 & + +script: + - ./gradlew clean connectedAndroidTest \ No newline at end of file diff --git a/README.md b/README.md index 5340bd6..6acc61b 100644 --- a/README.md +++ b/README.md @@ -1,170 +1,292 @@ -# AndroidTagView - -[![Build Status](https://travis-ci.org/whilu/AndroidTagView.svg)](https://travis-ci.org/whilu/AndroidTagView) [![Android Arsenal](https://img.shields.io/badge/Android%20Arsenal-AndroidTagView-green.svg?style=true)](https://android-arsenal.com/details/1/2992) - -An Android TagView library. You can customize awesome TagView by using this library. - -## Screenshots - -androidtagview_record_1.gif - -## Usage - -### Step 1 - -Add below dependency in your **build.gradle** file. - -```groovy -dependencies { - compile 'co.lujun:androidtagview:1.0.0' -} -``` - -### Step 2 - -Use the AndroidTagView in layout file, you can add customized attributes here. - -```xml - -``` - -### Step 3 - -Use TagView in your code. - -```java -TagContainerLayout mTagContainerLayout = (TagContainerLayout) findViewById(R.id.tagcontainerLayout); -mTagContainerLayout.setTags(List tags); -``` - -Now, you have successfully created some TagViews. The following will show some more useful features for you customize. - -## Attributes - -|name|format|description| -|:---:|:---:|:---:| -| vertical_interval | dimension | Vertical interval, default 5(dp) -| horizontal_interval | dimension | Horizontal interval, default 5(dp) -| container_border_width | dimension | TagContainerLayout border width(default 0.5dp) -| container_border_radius | dimension | TagContainerLayout border radius(default 10.0dp) -| container_border_color | color | TagContainerLayout border color(default #22FF0000) -| container_background_color | color | TagContainerLayout background color(default #11FF0000) -| container_enable_drag | boolean | Can drag TagView(default false) -| container_drag_sensitivity | float | The sensitive of the ViewDragHelper(default 1.0f, normal) -| tag_border_width | dimension | TagView Border width(default 0.5dp) -| tag_corner_radius | dimension | TagView Border radius(default 15.0dp) -| tag_horizontal_padding | dimension | Horizontal padding for TagView, include left and right padding(left and right padding are equal, default 20px) -| tag_vertical_padding | dimension | Vertical padding for TagView, include top and bottom padding(top and bottom padding are equal, default 17px) -| tag_text_size | dimension | TagView Text size(default 14sp) -| tag_text_color | color | TagView text color(default #FF666666) -| tag_border_color | color | TagView border color(default #88F44336) -| tag_background_color | color | TagView background color(default #33F44336) -| tag_max_length | integer | The max length for TagView(default max length 23) -| tag_clickable | boolean | Whether TagView can clickable(default unclickable) -| tag_theme | enum | The TagView [theme](#themes) - -**You can set these attributes in layout file, or use setters(each attribute has get and set method) to set them.** - -## Themes - -|theme|code|description -|:---:|:---:|:---:| -| none | ColorFactory.NONE | If you customize TagView with your way, set this theme -| random | ColorFactory.RANDOM | Create each TagView using random color -| pure_cyan | ColorFactory.PURE_CYAN | All TagView created by pure cyan color -| pure_teal | ColorFactory.PURE_TEAL | All TagView created by pure teal color - - -## Methods - -* Set a ```TagView.OnTagClickListener``` for TagView, for ```onTagClick``` and ```onTagLongClick``` callback -```java -mTagContainerLayout.setOnTagClickListener(new TagView.OnTagClickListener() { - - @Override - public void onTagClick(int position, String text) { - // ... - } - - @Override - public void onTagLongClick(final int position, String text) { - // ... - } -}); -``` -* Use ```setTagMaxLength(int max)``` to set text max length for all TagView. -```java -mTagContainerLayout.setTagMaxLength(int max); -``` -* Use ```getTagText(int position)``` to get TagView text at the specified location. -```java -String text = mTagContainerLayout.getTagText(int position); -``` -* If you set the attribute ```container_enable_drag``` to ```true```, when drag the TagView you can get latest state by using ```getTagViewState()```. There are 4 state:```ViewDragHelper.STATE_IDLE```, ```ViewDragHelper.STATE_DRAGGING```, and ```ViewDragHelper.STATE_SETTLING```. -```java -int state = mTagContainerLayout.getTagViewState(); -``` -* Set the [theme](#themes). If you want to customize theme, remember set theme with ```ColorFactory.NONE``` first, then set other attributes. -```java -// Set library provides theme -mTagContainerLayout.setTheme(ColorFactory.PURE_CYAN); -``` -```java -// Set customize theme -mTagContainerLayout.setTheme(ColorFactory.NONE); -mTagContainerLayout.setTagBackgroundColor(Color.TRANSPARENT); -``` - -**After set the attributes, set tags or add a tag.** - -* Use ```setTags()``` to set tags, require a parameter of type ```List``` or ```String[]```. -```java -mTagContainerLayout.setTags(List tags); -``` -* Insert a TagView into ContainerLayout at the end. -```java -mTagContainerLayout.addTag(String text); -``` -* Insert a TagView into ContainerLayout at the specified location, the TagView is inserted before the current element at the specified location. -```java -mTagContainerLayout.addTag(String text, int position); -``` -* Remove TagView on particular position, require the position of the TagView -```java -mTagContainerLayout.removeTag(int position); -``` - -## Change logs -###1.0.0 -- First release - -## Sample App -[APK](/sample/sample-release.apk) - -## About -If you have any questions, contact me: [lujun.byte#gmail.com](mailto:lujun.byte@gmail.com). - -## License - - Copyright 2015 lujun - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. \ No newline at end of file +# AndroidTagView + +[![Build Status](https://travis-ci.org/whilu/AndroidTagView.svg)](https://travis-ci.org/whilu/AndroidTagView) [![Android Arsenal](https://img.shields.io/badge/Android%20Arsenal-AndroidTagView-green.svg?style=true)](https://android-arsenal.com/details/1/2992) + +An Android TagView library. You can customize awesome TagView by using this library. + +## Screenshots + +androidtagview_record_1.gif device-2016-11-09-223523.png + +## Usage + +### Step 1 + +Add below dependency in your **build.gradle** file. + +```groovy +dependencies { + implementation 'co.lujun:androidtagview:1.1.7' + implementation 'androidx.appcompat:appcompat:1.0.1' +} +``` + +### Step 2 + +Use the AndroidTagView in layout file, you can add customized attributes here. + +```xml + +``` + +### Step 3 + +Use TagView in your code. + +```java +TagContainerLayout mTagContainerLayout = (TagContainerLayout) findViewById(R.id.tagcontainerLayout); +mTagContainerLayout.setTags(List tags); +``` + +Now, you have successfully created some TagViews. The following will show some more useful features for you customize. + +## Attributes + +|name|format|description| +|:---:|:---:|:---:| +| vertical_interval | dimension | Vertical interval, default 5(dp) +| horizontal_interval | dimension | Horizontal interval, default 5(dp) +| container_border_width | dimension | TagContainerLayout border width(default 0.5dp) +| container_border_radius | dimension | TagContainerLayout border radius(default 10.0dp) +| container_border_color | color | TagContainerLayout border color(default #22FF0000) +| container_background_color | color | TagContainerLayout background color(default #11FF0000) +| container_enable_drag | boolean | Can drag TagView(default false) +| container_drag_sensitivity | float | The sensitive of the ViewDragHelper(default 1.0f, normal) +| container_gravity | enum | The TagContainerLayout [gravity](#gravity) +| container_max_lines | integer | The max lines for TagContainerLayout(default 0, auto increase) +| tag_border_width | dimension | TagView Border width(default 0.5dp) +| tag_corner_radius | dimension | TagView Border radius(default 15.0dp) +| tag_horizontal_padding | dimension | Horizontal padding for TagView, include left and right padding(left and right padding are equal, default 10dp) +| tag_vertical_padding | dimension | Vertical padding for TagView, include top and bottom padding(top and bottom padding are equal, default 8dp) +| tag_text_size | dimension | TagView Text size(default 14sp) +| tag_bd_distance | dimension | The distance between baseline and descent(default 2.75dp) +| tag_text_color | color | TagView text color(default #FF666666) +| tag_border_color | color | TagView border color(default #88F44336) +| tag_background_color | color | TagView background color(default #33F44336) +| tag_max_length | integer | The max length for TagView(default max length 23) +| tag_clickable | boolean | Whether TagView can clickable(default false) +| tag_selectable | boolean | Whether TagView can be selectable(default false) +| tag_theme | enum | The TagView [theme](#themes) +| tag_text_direction | enum | The TagView text [direction](#directions) +| tag_ripple_color | color | The ripple effect color(default #EEEEEE) +| tag_ripple_alpha | integer | The ripple effect color alpha(the value may between 0 - 255, default 128) +| tag_ripple_duration | integer | The ripple effect duration(In milliseconds, default 1000ms) +| tag_enable_cross | boolean | Enable draw cross icon(default false) +| tag_cross_width | dimension | The cross area width(your cross click area, default equal to the TagView's height) +| tag_cross_color | color | The cross icon color(default Color.BLACK) +| tag_cross_line_width | dimension | The cross line width(default 1dp) +| tag_cross_area_padding | dimension | The padding of the cross area(default 10dp) +| tag_support_letters_rlt | boolean | Whether to support 'letters show with RTL(eg: Android -> diordnA)' style(default false) +| tag_background | reference | TagView background resource(default none background) + +**You can set these attributes in layout file, or use setters(each attribute has get and set method) to set them.** + +## Themes + +|theme|code|value|description +|:---:|:---:|:---:|:---:| +| none | ColorFactory.NONE | -1 | **If you customize TagView with your way, set this theme** +| random | ColorFactory.RANDOM | 0 | Create each TagView using random color +| pure_cyan | ColorFactory.PURE_CYAN | 1 | All TagView created by pure cyan color +| pure_teal | ColorFactory.PURE_TEAL | 2 | All TagView created by pure teal color + +## Directions + +|direction|code|value|description +|:---:|:---:|:---:|:---:| +| ltr | View.TEXT_DIRECTION_LTR | 3 | Text direction is forced to LTR(default) +| rtl | View.TEXT_DIRECTION_RTL | 4 | Text direction is forced to RTL + +## Gravity + +|gravity|code|value|description +|:---:|:---:|:---:|:---:| +| left | Gravity.LEFT | 3 | Push TagView to the left of TagContainerLayout(default) +| center | Gravity.CENTER | 17 | Push TagView to the center of TagContainerLayout +| right | Gravity.RIGHT | 5 | Push TagView to the right of TagContainerLayout + +## Methods + +* Set a ```TagView.OnTagClickListener``` for TagView, for ```onTagClick``` , ```onTagLongClick``` and ```onTagCrossClick``` callback +```java +mTagContainerLayout.setOnTagClickListener(new TagView.OnTagClickListener() { + + @Override + public void onTagClick(int position, String text) { + // ... + } + + @Override + public void onTagLongClick(final int position, String text) { + // ... + } + + @Override + public void onSelectedTagDrag(int position, String text){ + // ... + } + + @Override + public void onTagCrossClick(int position) { + // ... + } +}); +``` +* Use ```setTagMaxLength(int max)``` to set text max length for all TagView. +```java +mTagContainerLayout.setTagMaxLength(int max); +``` +* Use ```getTagText(int position)``` to get TagView text at the specified location. +```java +String text = mTagContainerLayout.getTagText(int position); +``` +* ```getTags()``` return a string list for all tags in TagContainerLayout. +```java +List list = mTagContainerLayout.getTags(); +``` +* If you set the attribute ```container_enable_drag``` to ```true```, when drag the TagView you can get latest state by using ```getTagViewState()```. There are 4 state:```ViewDragHelper.STATE_IDLE```, ```ViewDragHelper.STATE_DRAGGING```, and ```ViewDragHelper.STATE_SETTLING```. +```java +int state = mTagContainerLayout.getTagViewState(); +``` +* Set the [theme](#themes). If you want to customize theme, remember set theme with ```ColorFactory.NONE``` first, then set other attributes. +```java +// Set library provides theme +mTagContainerLayout.setTheme(ColorFactory.PURE_CYAN); +``` +```java +// Set customize theme +mTagContainerLayout.setTheme(ColorFactory.NONE); +mTagContainerLayout.setTagBackgroundColor(Color.TRANSPARENT); +``` +* Set the text [direction](#directions). The library support two direction ```View.TEXT_DIRECTION_LTR``` and ```View.TEXT_DIRECTION_RTL```. +```java +mTagContainerLayout.setTagTextDirection(View.TEXT_DIRECTION_RTL); +``` +* Use ```setTagTypeface(Typeface typeface)``` to set TagView text typeface. +```java +Typeface typeface = Typeface.createFromAsset(getAssets(), "iran_sans.ttf"); +mTagContainerLayout.setTagTypeface(typeface); +``` + +**After set the attributes, set tags or add a tag.** + +* Use ```setTags()``` to set tags, require a parameter of type ```List``` or ```String[]```. +```java +mTagContainerLayout.setTags(List tags); +``` +* Insert a TagView into ContainerLayout at the end. +```java +mTagContainerLayout.addTag(String text); +``` +* Insert a TagView into ContainerLayout at the specified location, the TagView is inserted before the current element at the specified location. +```java +mTagContainerLayout.addTag(String text, int position); +``` +* Remove TagView on particular position, require the position of the TagView. +```java +mTagContainerLayout.removeTag(int position); +``` +* Remove all TagViews. +```java +mTagContainerLayout.removeAllTags(); +``` +* Get a TagView in specified position. +```java +mTagContainerLayout.getTagView(int position); +``` +* Set color for each TagView. +```java +List colors = new ArrayList(); +//int[] color = {TagBackgroundColor, TabBorderColor, TagTextColor, TagSelectedBackgroundColor} +int[] color1 = {Color.RED, Color.BLACK, Color.WHITE, Color.YELLOW}; +int[] color2 = {Color.BLUE, Color.BLACK, Color.WHITE, Color.YELLOW}; +colors.add(color1); +colors.add(color2); +mTagcontainerLayout.setTags(tags, colors); +``` + +## Change logs + +### 1.1.7(2019-01-21) +- Fix bugs + +### 1.1.6(2018-12-1) +- Support tag selectable + +### 1.1.5(2018-8-20) +- Allow images on tags (in LTR languages). + +### 1.1.4(2017-6-1) +- Add attribute for TagView background. + +### 1.1.3(2017-5-17) +- Add ```getTagView(int position)``` method to get TagView in specified position. + +### 1.1.2(2017-5-16) +- Fix bugs + +### 1.1.1(2017-4-16) +- Customize the color of the TagView, see [#51](https://github.com/whilu/AndroidTagView/pull/51) +- Fixed issue [#50](https://github.com/whilu/AndroidTagView/issues/50), [#49](https://github.com/whilu/AndroidTagView/issues/49) + +### 1.1.0(2017-3-5) +- Fixed issue [#45](https://github.com/whilu/AndroidTagView/issues/45) +- Support 'letters show with RTL(eg: Android -> diordnA)' style + +### 1.0.6(2017-2-14) +- Fix bugs + +### 1.0.5(2016-11-9) +- Add cross view for TagView + +### 1.0.4(2016-10-30) +- Support ripple effect(Call requires API level 11), like [Android CustomButton](https://github.com/whilu/AndroidSample/tree/master/CustomButton) +- Fix bugs + +### 1.0.3(2016-4-3) +- Add ```getTags()``` method to get the list for all tags +- Fixed bugs in ListView/RecyclerView + +### 1.0.2(2016-1-18) +- Support [gravity](#gravity) for ```TagContainerLayout``` +- Support set typeface + +### 1.0.1(2016-1-14) +- Support text [direction](#directions) +- Add ```removeAllTags()``` method for remove all TagViews +- Fixed issue [#1](https://github.com/whilu/AndroidTagView/issues/1) +- Fixed other bugs + +### 1.0.0(2016-1-6) +- First release + +## Sample App +[APK](/sample/sample-release.apk) + +## About +If you have any questions, contact me: [lujun.byte#gmail.com](mailto:lujun.byte@gmail.com). + +## License + + Copyright 2015 lujun + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/androidtagview/.gitignore b/androidtagview/.gitignore index fbe08b1..6c9b4c3 100755 --- a/androidtagview/.gitignore +++ b/androidtagview/.gitignore @@ -1,46 +1,46 @@ -.gradle -/local.properties -/.idea/workspace.xml -.DS_Store -/build -# built application files -*.ap_ - -# files for the dex VM -*.dex - -# Java class files -*.class -.DS_Store - -# generated files -bin/ -gen/ -Wiki/ - -# Local configuration file (sdk path, etc) -local.properties - -# Eclipse project files -.classpath -.project -.settings/ - -# Proguard folder generated by Eclipse -proguard/ - -#Android Studio -build/ -src/androidTest/ - -# Intellij project files -*.iml -*.ipr -*.iws -.idea/ - -#gradle -.gradle/ -.idea -app/build +.gradle +/local.properties +/.idea/workspace.xml +.DS_Store +/build +# built application files +*.ap_ + +# files for the dex VM +*.dex + +# Java class files +*.class +.DS_Store + +# generated files +bin/ +gen/ +Wiki/ + +# Local configuration file (sdk path, etc) +local.properties + +# Eclipse project files +.classpath +.project +.settings/ + +# Proguard folder generated by Eclipse +proguard/ + +#Android Studio +build/ +src/androidTest/ + +# Intellij project files +*.iml +*.ipr +*.iws +.idea/ + +#gradle +.gradle/ +.idea +app/build app/src/androidTest \ No newline at end of file diff --git a/androidtagview/build.gradle b/androidtagview/build.gradle index 9119f90..a04e40a 100644 --- a/androidtagview/build.gradle +++ b/androidtagview/build.gradle @@ -1,26 +1,25 @@ -apply plugin: 'com.android.library' - -android { - compileSdkVersion 23 - buildToolsVersion "22.0.1" - - defaultConfig { - minSdkVersion 9 - targetSdkVersion 23 - versionCode 100 - versionName "1.0.0" - } - buildTypes { - release { - minifyEnabled false - proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' - } - } -} - -dependencies { - compile fileTree(dir: 'libs', include: ['*.jar']) - testCompile 'junit:junit:4.12' - compile 'com.android.support:appcompat-v7:23.1.1' -} +apply plugin: 'com.android.library' + +android { + compileSdkVersion 28 + + defaultConfig { + minSdkVersion 14 + targetSdkVersion 28 + versionCode 117 + versionName "1.1.7" + } + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } +} + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) + testImplementation 'junit:junit:4.12' + implementation 'androidx.appcompat:appcompat:1.0.1' +} apply from: 'https://raw.githubusercontent.com/whilu/AndroidPublishLibrary/master/project/library/bintray_publish.gradle' \ No newline at end of file diff --git a/androidtagview/proguard-rules.pro b/androidtagview/proguard-rules.pro index 700c8df..6828981 100644 --- a/androidtagview/proguard-rules.pro +++ b/androidtagview/proguard-rules.pro @@ -1,17 +1,17 @@ -# Add project specific ProGuard rules here. -# By default, the flags in this file are appended to flags specified -# in F:\code\android\adt-bundle-windows-x86_64-20140702\sdk/tools/proguard/proguard-android.txt -# You can edit the include path and order by changing the proguardFiles -# directive in build.gradle. -# -# For more details, see -# http://developer.android.com/guide/developing/tools/proguard.html - -# Add any project specific keep options here: - -# If your project uses WebView with JS, uncomment the following -# and specify the fully qualified class name to the JavaScript interface -# class: -#-keepclassmembers class fqcn.of.javascript.interface.for.webview { -# public *; -#} +# Add project specific ProGuard rules here. +# By default, the flags in this file are appended to flags specified +# in F:\code\android\adt-bundle-windows-x86_64-20140702\sdk/tools/proguard/proguard-android.txt +# You can edit the include path and order by changing the proguardFiles +# directive in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# Add any project specific keep options here: + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} diff --git a/androidtagview/project.properties b/androidtagview/project.properties new file mode 100644 index 0000000..1a8d96f --- /dev/null +++ b/androidtagview/project.properties @@ -0,0 +1,10 @@ +#project +project.name=AndroidTagView +project.groupId=co.lujun +project.artifactId=androidtagview +project.packaging=aar +project.siteUrl=https://github.com/whilu/AndroidTagView +project.gitUrl=https://github.com/whilu/AndroidTagView.git + +#javadoc +javadoc.name=AndroidTagView \ No newline at end of file diff --git a/androidtagview/src/main/AndroidManifest.xml b/androidtagview/src/main/AndroidManifest.xml index 3cf689e..4b17c55 100644 --- a/androidtagview/src/main/AndroidManifest.xml +++ b/androidtagview/src/main/AndroidManifest.xml @@ -1,11 +1,8 @@ - - - - - - - + + + + + + + diff --git a/androidtagview/src/main/java/co/lujun/androidtagview/ColorFactory.java b/androidtagview/src/main/java/co/lujun/androidtagview/ColorFactory.java index 4052232..f7871b7 100644 --- a/androidtagview/src/main/java/co/lujun/androidtagview/ColorFactory.java +++ b/androidtagview/src/main/java/co/lujun/androidtagview/ColorFactory.java @@ -1,3 +1,19 @@ +/* + * Copyright 2015 lujun + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package co.lujun.androidtagview; import android.graphics.Color; @@ -9,9 +25,9 @@ public class ColorFactory { /** - * ============= -->border color - * background color<---||- Text --||-->text color - * ============= + * ============= --border color + * background color---||- Text --||--text color + * ============= */ public static final String BG_COLOR_ALPHA = "33"; @@ -49,7 +65,8 @@ public static int[] onRandomBuild(){ int bgColor = Color.parseColor("#" + BG_COLOR_ALPHA + COLORS[random]); int bdColor = Color.parseColor("#" + BD_COLOR_ALPHA + COLORS[random]); int tColor = SHARP666666; - return new int[]{bgColor, bdColor, tColor}; + int tColor2 = SHARP727272; + return new int[]{bgColor, bdColor, tColor, tColor2}; } public static int[] onPureBuild(PURE_COLOR type){ @@ -57,7 +74,8 @@ public static int[] onPureBuild(PURE_COLOR type){ int bgColor = Color.parseColor("#" + BG_COLOR_ALPHA + color); int bdColor = Color.parseColor("#" + BD_COLOR_ALPHA + color); int tColor = SHARP727272; - return new int[]{bgColor, bdColor, tColor}; + int tColor2 = SHARP666666; + return new int[]{bgColor, bdColor, tColor, tColor2}; } } diff --git a/androidtagview/src/main/java/co/lujun/androidtagview/TagContainerLayout.java b/androidtagview/src/main/java/co/lujun/androidtagview/TagContainerLayout.java index ed874a8..e36dccf 100644 --- a/androidtagview/src/main/java/co/lujun/androidtagview/TagContainerLayout.java +++ b/androidtagview/src/main/java/co/lujun/androidtagview/TagContainerLayout.java @@ -1,832 +1,1611 @@ -package co.lujun.androidtagview; - -import android.content.Context; -import android.content.res.TypedArray; -import android.graphics.Canvas; -import android.graphics.Color; -import android.graphics.Paint; -import android.graphics.RectF; -import android.support.v4.widget.ViewDragHelper; -import android.util.AttributeSet; -import android.view.MotionEvent; -import android.view.View; -import android.view.ViewGroup; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -/** - * Author: lujun(http://blog.lujun.co) - * Date: 2015-12-30 17:14 - */ -public class TagContainerLayout extends ViewGroup { - - /** Vertical interval, default 5(dp)*/ - private int mVerticalInterval; - - /** Horizontal interval, default 5(dp)*/ - private int mHorizontalInterval; - - /** TagContainerLayout border width(default 0.5dp)*/ - private float mBorderWidth = 0.5f; - - /** TagContainerLayout border radius(default 10.0dp)*/ - private float mBorderRadius = 10.0f; - - /** The sensitive of the ViewDragHelper(default 1.0f, normal)*/ - private float mSensitivity = 1.0f; - - /** Tagview average height*/ - private int mChildHeight; - - /** TagContainerLayout border color(default #22FF0000)*/ - private int mBorderColor = Color.parseColor("#22FF0000"); - - /** TagContainerLayout background color(default #11FF0000)*/ - private int mBackgroundColor = Color.parseColor("#11FF0000"); - - /** The max length for TagView(default max length 23)*/ - private int mTagMaxLength = 23; - - /** TagView Border width(default 0.5dp)*/ - private float mTagBorderWidth = 0.5f; - - /** TagView Border radius(default 15.0dp)*/ - private float mTagBorderRadius = 15.0f; - - /** TagView Text size(default 14sp)*/ - private float mTagTextSize = 14; - - /** Horizontal padding for TagView, include left & right padding(left & right padding are equal, default 20px)*/ - private int mTagHorizontalPadding = 20; - - /** Vertical padding for TagView, include top & bottom padding(top & bottom padding are equal, default 17px)*/ - private int mTagVerticalPadding = 17; - - /** TagView border color(default #88F44336)*/ - private int mTagBorderColor = Color.parseColor("#88F44336"); - - /** TagView background color(default #33F44336)*/ - private int mTagBackgroundColor = Color.parseColor("#33F44336"); - - /** TagView text color(default #FF666666)*/ - private int mTagTextColor = Color.parseColor("#FF666666"); - - /** Whether TagView can clickable(default unclickable)*/ - private boolean isTagViewClickable; - - /** Tags*/ - private List mTags; - - /** Can drag TagView(default false)*/ - private boolean mDragEnable; - - /** TagView drag state(default STATE_IDLE)*/ - private int mTagViewState = ViewDragHelper.STATE_IDLE; - - /** OnTagClickListener for TagView*/ - private TagView.OnTagClickListener mOnTagClickListener; - - private Paint mPaint; - - private RectF mRectF; - - private ViewDragHelper mViewDragHelper; - - private List mChildViews; - - private int[] mViewPos; - - /** View theme(default PURE_CYAN)*/ - private int mTheme = 1; - - /** Default interval(dp)*/ - private static final float DEFAULT_INTERVAL = 5; - - /** Default tag min length*/ - private static final int TAG_MIN_LENGTH = 3; - - public TagContainerLayout(Context context) { - this(context, null); - } - - public TagContainerLayout(Context context, AttributeSet attrs) { - this(context, attrs, 0); - } - - public TagContainerLayout(Context context, AttributeSet attrs, int defStyleAttr){ - super(context, attrs, defStyleAttr); - init(context, attrs, defStyleAttr); - } - - private void init(Context context, AttributeSet attrs, int defStyleAttr){ - TypedArray attributes = context.obtainStyledAttributes(attrs, R.styleable.AndroidTagView, - defStyleAttr, 0); - mVerticalInterval = (int)attributes.getDimension(R.styleable.AndroidTagView_vertical_interval, - dp2px(context, DEFAULT_INTERVAL)); - mHorizontalInterval = (int)attributes.getDimension(R.styleable.AndroidTagView_horizontal_interval, - dp2px(context, DEFAULT_INTERVAL)); - mBorderWidth = attributes.getDimension(R.styleable.AndroidTagView_container_border_width, - dp2px(context, mBorderWidth)); - mBorderRadius = attributes.getDimension(R.styleable.AndroidTagView_container_border_radius, - dp2px(context, mBorderRadius)); - mBorderColor = attributes.getColor(R.styleable.AndroidTagView_container_border_color, - mBorderColor); - mBackgroundColor = attributes.getColor(R.styleable.AndroidTagView_container_background_color, - mBackgroundColor); - mDragEnable = attributes.getBoolean(R.styleable.AndroidTagView_container_enable_drag, false); - mSensitivity = attributes.getFloat(R.styleable.AndroidTagView_container_drag_sensitivity, - mSensitivity); - mTagMaxLength = attributes.getInt(R.styleable.AndroidTagView_tag_max_length, mTagMaxLength); - mTheme = attributes.getInt(R.styleable.AndroidTagView_tag_theme, mTheme); - mTagBorderWidth = attributes.getDimension(R.styleable.AndroidTagView_tag_border_width, - dp2px(context, mTagBorderWidth)); - mTagBorderRadius = attributes.getDimension( - R.styleable.AndroidTagView_tag_corner_radius, dp2px(context, mTagBorderRadius)); - mTagHorizontalPadding = (int) attributes.getDimension( - R.styleable.AndroidTagView_tag_horizontal_padding, mTagHorizontalPadding); - mTagVerticalPadding = (int) attributes.getDimension( - R.styleable.AndroidTagView_tag_vertical_padding, mTagVerticalPadding); - mTagTextSize = attributes.getDimension(R.styleable.AndroidTagView_tag_text_size, - sp2px(context, mTagTextSize)); - mTagBorderColor = attributes.getColor(R.styleable.AndroidTagView_tag_border_color, - mTagBorderColor); - mTagBackgroundColor = attributes.getColor(R.styleable.AndroidTagView_tag_background_color, - mTagBackgroundColor); - mTagTextColor = attributes.getColor(R.styleable.AndroidTagView_tag_text_color, mTagTextColor); - isTagViewClickable = attributes.getBoolean(R.styleable.AndroidTagView_tag_clickable, false); - attributes.recycle(); - - mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); - mRectF = new RectF(); - mChildViews = new ArrayList(); - mViewDragHelper = ViewDragHelper.create(this, mSensitivity, new DragHelperCallBack()); - setWillNotDraw(false); - setDragState(); - setTagMaxLength(mTagMaxLength); - setTagHorizontalPadding(mTagHorizontalPadding); - setTagVerticalPadding(mTagVerticalPadding); - } - - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - super.onMeasure(widthMeasureSpec, heightMeasureSpec); - - measureChildren(widthMeasureSpec, heightMeasureSpec); - final int childCount = getChildCount(); - int lines = childCount == 0 ? 0 : getChildLines(childCount); - int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec); - int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec); - int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec); - int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec); - - if (childCount == 0){ - setMeasuredDimension(0, 0); - }else if (heightSpecMode == MeasureSpec.AT_MOST) { - setMeasuredDimension(widthSpecSize, (mVerticalInterval + mChildHeight) * lines - - mVerticalInterval + getPaddingTop() + getPaddingBottom()); - }else { - setMeasuredDimension(widthSpecSize, heightSpecSize); - } - } - - @Override - protected void onLayout(boolean changed, int l, int t, int r, int b) { - int availableW = getMeasuredWidth() - getPaddingLeft() - getPaddingRight(); - int childCount = getChildCount(); - int curLeft = getPaddingLeft(), curTop = getPaddingTop(); - mViewPos = new int[childCount * 2]; - for (int i = 0; i < childCount; i++) { - final View childView = getChildAt(i); - if (childView.getVisibility() != GONE) { - int width = childView.getMeasuredWidth(); - if (curLeft + width + mHorizontalInterval - getPaddingLeft() > availableW){ - curLeft = getPaddingLeft(); - curTop += mChildHeight + mVerticalInterval; - } - mViewPos[i * 2] = curLeft; - mViewPos[i * 2 + 1] = curTop; - childView.layout(curLeft, curTop, curLeft + width, curTop + mChildHeight); - curLeft += width + mHorizontalInterval; - } - } - } - - @Override - protected void onDraw(Canvas canvas) { - super.onDraw(canvas); - mRectF.set(canvas.getClipBounds().left, canvas.getClipBounds().top, - canvas.getClipBounds().right, canvas.getClipBounds().bottom); - - mPaint.setStyle(Paint.Style.FILL); - mPaint.setColor(mBackgroundColor); - canvas.drawRoundRect(mRectF, mBorderRadius, mBorderRadius, mPaint); - - mPaint.setStyle(Paint.Style.STROKE); - mPaint.setStrokeWidth(mBorderWidth); - mPaint.setColor(mBorderColor); - canvas.drawRoundRect(mRectF, mBorderRadius, mBorderRadius, mPaint); - } - - @Override - public boolean onInterceptTouchEvent(MotionEvent ev) { - return mViewDragHelper.shouldInterceptTouchEvent(ev); - } - - @Override - public boolean onTouchEvent(MotionEvent event) { - mViewDragHelper.processTouchEvent(event); - return true; - } - - @Override - public void computeScroll() { - super.computeScroll(); - if (mViewDragHelper.continueSettling(true)){ - requestLayout(); - } - } - - private int getChildLines(int childCount){ - int availableW = getMeasuredWidth() - getPaddingLeft() - getPaddingRight(); - int lines = 1; - for (int i = 0, curLineW = 0; i < childCount; i++) { - View childView = getChildAt(i); - int dis = childView.getMeasuredWidth() + (int) mHorizontalInterval; - int height = childView.getMeasuredHeight(); - mChildHeight = i == 0 ? height : Math.min(mChildHeight, height); - curLineW += dis; - if (curLineW > availableW){ - lines++; - curLineW = dis; - } - } - return lines; - } - - private int[] onUpdateColorFactory(){ - int[] colors; - if (mTheme == ColorFactory.RANDOM){ - colors = ColorFactory.onRandomBuild(); - }else if (mTheme == ColorFactory.PURE_TEAL){ - colors = ColorFactory.onPureBuild(ColorFactory.PURE_COLOR.TEAL); - }else if (mTheme == ColorFactory.PURE_CYAN){ - colors = ColorFactory.onPureBuild(ColorFactory.PURE_COLOR.CYAN); - }else { - colors = new int[]{mTagBackgroundColor, mTagBorderColor, mTagTextColor}; - } - return colors; - } - - private void onSetTag(){ - if (mTags == null || mTags.size() == 0){ - return; - } - for (int i = 0; i < mTags.size(); i++) { - onAddTag(mTags.get(i), mChildViews.size()); - } - postInvalidate(); - } - - private void onAddTag(String text, int position) { - if (position < 0 || position > mChildViews.size()){ - throw new RuntimeException("Illegal position!"); - } - TagView tagView = new TagView(getContext(), text); - initTagView(tagView); - mChildViews.add(position, tagView); - if (position < mChildViews.size()){ - for (int i = position; i < mChildViews.size(); i++) { - mChildViews.get(i).setTag(i); - } - }else { - tagView.setTag(position); - } - addView(tagView, position); - } - - private void initTagView(TagView tagView){ - tagView.setTagMaxLength(mTagMaxLength); - tagView.setOnTagClickListener(mOnTagClickListener); - int[] colors = onUpdateColorFactory(); - tagView.setTagBackgroundColor(colors[0]); - tagView.setTagBorderColor(colors[1]); - tagView.setTagTextColor(colors[2]); - tagView.setBorderWidth(mTagBorderWidth); - tagView.setBorderRadius(mTagBorderRadius); - tagView.setTextSize(mTagTextSize); - tagView.setHorizontalPadding(mTagHorizontalPadding); - tagView.setVerticalPadding(mTagVerticalPadding); - tagView.setIsViewClickable(isTagViewClickable); - } - - private void onRemoveTag(int position){ - if (position < 0 || position >= mChildViews.size()){ - throw new RuntimeException("Illegal position!"); - } - mChildViews.remove(position); - removeViewAt(position); - for (int i = position; i < mChildViews.size(); i++) { - mChildViews.get(i).setTag(i); - } - // TODO, make removed view null? - } - - private int[] onGetNewPosition(View view){ - int left = view.getLeft(); - int top = view.getTop(); - int bestMatchLeft = mViewPos[(int)view.getTag() * 2]; - int bestMatchTop = mViewPos[(int)view.getTag() * 2 + 1]; - int tmpTopDis = Math.abs(top - bestMatchTop); - for (int i = 0; i < mViewPos.length / 2; i++) { - if (Math.abs(top - mViewPos[i * 2 +1]) < tmpTopDis){ - bestMatchTop = mViewPos[i * 2 +1]; - tmpTopDis = Math.abs(top - mViewPos[i * 2 +1]); - } - } - int rowChildCount = 0; - int tmpLeftDis = 0; - for (int i = 0; i < mViewPos.length / 2; i++) { - if (mViewPos[i * 2 + 1] == bestMatchTop){ - if (rowChildCount == 0){ - bestMatchLeft = mViewPos[i * 2]; - tmpLeftDis = Math.abs(left - bestMatchLeft); - }else { - if (Math.abs(left - mViewPos[i * 2]) < tmpLeftDis){ - bestMatchLeft = mViewPos[i * 2]; - tmpLeftDis = Math.abs(left - bestMatchLeft); - } - } - rowChildCount++; - } - } - return new int[]{bestMatchLeft, bestMatchTop}; - } - - private int onGetCoordinateReferPos(int left, int top){ - int pos = 0; - for (int i = 0; i < mViewPos.length / 2; i++) { - if (left == mViewPos[i * 2] && top == mViewPos[i * 2 + 1]){ - pos = i; - } - } - return pos; - } - - private void setDragState(){ - mTagViewState = mDragEnable ? ViewDragHelper.STATE_DRAGGING : ViewDragHelper.STATE_IDLE; - } - - private void onChangeView(View view, int newPos, int originPos){ - mChildViews.remove(originPos); - mChildViews.add(newPos, view); - for (View child : mChildViews) { - child.setTag(mChildViews.indexOf(child)); - } - - removeViewAt(originPos); - addView(view, newPos); - } - - private int ceilTagBorderWidth(){ - return (int)Math.ceil(mTagBorderWidth); - } - - private class DragHelperCallBack extends ViewDragHelper.Callback{ - - @Override - public void onViewDragStateChanged(int state) { - super.onViewDragStateChanged(state); - mTagViewState = state; - } - - @Override - public boolean tryCaptureView(View child, int pointerId) { - return mDragEnable; - } - - @Override - public int clampViewPositionHorizontal(View child, int left, int dx) { - final int leftX = getPaddingLeft(); - final int rightX = getWidth() - child.getWidth() - getPaddingRight(); - return Math.min(Math.max(left, leftX), rightX); - } - - @Override - public int clampViewPositionVertical(View child, int top, int dy) { - final int topY = getPaddingTop(); - final int bottomY = getHeight() - child.getHeight() - getPaddingBottom(); - return Math.min(Math.max(top, topY), bottomY); - } - - @Override - public int getViewHorizontalDragRange(View child) { - return getMeasuredWidth() - child.getMeasuredWidth(); - } - - @Override - public int getViewVerticalDragRange(View child) { - return getMeasuredHeight() - child.getMeasuredHeight(); - } - - @Override - public void onViewReleased(View releasedChild, float xvel, float yvel) { - super.onViewReleased(releasedChild, xvel, yvel); - int[] pos = onGetNewPosition(releasedChild); - int posRefer = onGetCoordinateReferPos(pos[0], pos[1]); - onChangeView(releasedChild, posRefer, (int) releasedChild.getTag()); - mViewDragHelper.settleCapturedViewAt(pos[0], pos[1]); - invalidate(); - } - } - - /** - * Get current drag view state. - * @return - */ - public int getTagViewState(){ - return mTagViewState; - } - /** - * Set tags - * @param tags - */ - public void setTags(List tags){ - mTags = tags; - onSetTag(); - } - - /** - * Set tags - * @param tags - */ - public void setTags(String... tags){ - mTags = Arrays.asList(tags); - onSetTag(); - } - - /** - * Inserts the specified TagView into this ContainerLayout at the end. - * @param text - */ - public void addTag(String text){ - addTag(text, mChildViews.size()); - } - - /** - * Inserts the specified TagView into this ContainerLayout at the specified location. - * The TagView is inserted before the current element at the specified location. - * @param text - * @param position - */ - public void addTag(String text, int position){ - onAddTag(text, position); - postInvalidate(); - } - - /** - * Remove a TagView in specified position. - * @param position - */ - public void removeTag(int position){ - onRemoveTag(position); - postInvalidate(); - } - - /** - * Set OnTagClickListener for TagView. - * @param listener - */ - public void setOnTagClickListener(TagView.OnTagClickListener listener){ - mOnTagClickListener = listener; - } - - /** - * Get TagView text. - * @param position - * @return - */ - public String getTagText(int position){ - return ((TagView)mChildViews.get(position)).getText(); - } - - /** - * Set whether the child view can be dragged. - * @param enable - */ - public void setDragEnable(boolean enable){ - this.mDragEnable = enable; - setDragState(); - } - - /** - * Get current view is drag enable attribute. - * @return - */ - public boolean getDragEnable(){ - return mDragEnable; - } - - /** - * Set vertical interval - * @param interval - */ - public void setVerticalInterval(float interval){ - mVerticalInterval = (int) dp2px(getContext(), interval); - postInvalidate(); - } - - /** - * Get vertical interval in this view. - * @return - */ - public int getVerticalInterval(){ - return mVerticalInterval; - } - - /** - * Set horizontal interval. - * @param interval - */ - public void setHorizontalInterval(float interval){ - mHorizontalInterval = (int)dp2px(getContext(), interval); - postInvalidate(); - } - - /** - * Get horizontal interval in this view. - * @return - */ - public int getHorizontalInterval(){ - return mHorizontalInterval; - } - - /** - * Get TagContainerLayout border width. - * @return - */ - public float getBorderWidth() { - return mBorderWidth; - } - - /** - * Set TagContainerLayout border width. - * @param width - */ - public void setBorderWidth(float width) { - this.mBorderWidth = width; - } - - /** - * Get TagContainerLayout border radius. - * @return - */ - public float getBorderRadius() { - return mBorderRadius; - } - - /** - * Set TagContainerLayout border radius. - * @param radius - */ - public void setBorderRadius(float radius) { - this.mBorderRadius = radius; - } - - /** - * Get TagContainerLayout border color. - * @return - */ - public int getBorderColor() { - return mBorderColor; - } - - /** - * Set TagContainerLayout border color. - * @param color - */ - public void setBorderColor(int color) { - this.mBorderColor = color; - } - - /** - * Get TagContainerLayout background color. - * @return - */ - public int getBackgroundColor() { - return mBackgroundColor; - } - - /** - * Set TagContainerLayout background color. - * @param color - */ - public void setBackgroundColor(int color) { - this.mBackgroundColor = color; - } - - /** - * Get TagContainerLayout ViewDragHelper sensitivity. - * @return - */ - public float getSensitivity() { - return mSensitivity; - } - - /** - * Set TagContainerLayout ViewDragHelper sensitivity. - * @param sensitivity - */ - public void setSensitivity(float sensitivity) { - this.mSensitivity = sensitivity; - } - - /** - * Set the TagView text max length(must >=3). - * @param maxLength - */ - public void setTagMaxLength(int maxLength){ - mTagMaxLength = maxLength < TAG_MIN_LENGTH ? TAG_MIN_LENGTH : maxLength; - } - - /** - * Get TagView max length. - * @return - */ - public int getTagMaxLength(){ - return mTagMaxLength; - } - - /** - * Set TagView theme. - * @param theme - */ - public void setTheme(int theme){ - mTheme = theme; - } - - /** - * Get TagView theme. - * @return - */ - public int getTheme(){ - return mTheme; - } - - /** - * Get TagView is clickable. - * @return - */ - public boolean getIsTagViewClickable() { - return isTagViewClickable; - } - - /** - * Set TagView is clickable - * @param clickable - */ - public void setIsTagViewClickable(boolean clickable) { - this.isTagViewClickable = clickable; - } - - /** - * Get TagView border width. - * @return - */ - public float getTagBorderWidth() { - return mTagBorderWidth; - } - - /** - * Set TagView border width. - * @param width - */ - public void setTagBorderWidth(float width) { - this.mTagBorderWidth = width; - } - - /** - * Get TagView border radius. - * @return - */ - public float getTagBorderRadius() { - return mTagBorderRadius; - } - - /** - * Set TagView border radius. - * @param radius - */ - public void setTagBorderRadius(float radius) { - this.mTagBorderRadius = radius; - } - - /** - * Get TagView text size. - * @return - */ - public float getTagTextSize() { - return mTagTextSize; - } - - /** - * Set TagView text size. - * @param size - */ - public void setTagTextSize(float size) { - this.mTagTextSize = size; - } - - /** - * Get TagView horizontal padding. - * @return - */ - public int getTagHorizontalPadding() { - return mTagHorizontalPadding; - } - - /** - * Set TagView horizontal padding. - * @param padding - */ - public void setTagHorizontalPadding(int padding) { - int ceilWidth = ceilTagBorderWidth(); - this.mTagHorizontalPadding = padding < ceilWidth ? ceilWidth : padding; - } - - /** - * Get TagView vertical padding. - * @return - */ - public int getTagVerticalPadding() { - return mTagVerticalPadding; - } - - /** - * Set TagView vertical padding. - * @param padding - */ - public void setTagVerticalPadding(int padding) { - int ceilWidth = ceilTagBorderWidth(); - this.mTagVerticalPadding = padding < ceilWidth ? ceilWidth : padding; - } - - /** - * Get TagView border color. - * @return - */ - public int getTagBorderColor() { - return mTagBorderColor; - } - - /** - * Set TagView border color. - * @param color - */ - public void setTagBorderColor(int color) { - this.mTagBorderColor = color; - } - - /** - * Get TagView background color. - * @return - */ - public int getTagBackgroundColor() { - return mTagBackgroundColor; - } - - /** - * Set TagView background color. - * @param color - */ - public void setTagBackgroundColor(int color) { - this.mTagBackgroundColor = color; - } - - /** - * Get TagView text color. - * @return - */ - public int getTagTextColor() { - return mTagTextColor; - } - - /** - * Set TagView text color. - * @param color - */ - public void setTagTextColor(int color) { - this.mTagTextColor = color; - } - - public float dp2px(Context context, float dp) { - final float scale = context.getResources().getDisplayMetrics().density; - return dp * scale + 0.5f; - } - - public float sp2px(Context context, float sp) { - final float scale = context.getResources().getDisplayMetrics().scaledDensity; - return sp * scale; - } -} +/* + * Copyright 2015 lujun + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package co.lujun.androidtagview; + +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.RectF; +import android.graphics.Typeface; +import android.util.AttributeSet; +import android.view.Gravity; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewGroup; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import androidx.annotation.DrawableRes; +import androidx.customview.widget.ViewDragHelper; + +import static co.lujun.androidtagview.Utils.dp2px; +import static co.lujun.androidtagview.Utils.sp2px; + +/** + * Author: lujun(http://blog.lujun.co) + * Date: 2015-12-30 17:14 + */ +public class TagContainerLayout extends ViewGroup { + + /** + * Vertical interval, default 5(dp) + */ + private int mVerticalInterval; + + /** + * The list to store the tags color info + */ + private List mColorArrayList; + + /** + * Horizontal interval, default 5(dp) + */ + private int mHorizontalInterval; + + /** + * TagContainerLayout border width(default 0.5dp) + */ + private float mBorderWidth = 0.5f; + + /** + * TagContainerLayout border radius(default 10.0dp) + */ + private float mBorderRadius = 10.0f; + + /** + * The sensitive of the ViewDragHelper(default 1.0f, normal) + */ + private float mSensitivity = 1.0f; + + /** + * TagView average height + */ + private int mChildHeight; + + /** + * TagContainerLayout border color(default #22FF0000) + */ + private int mBorderColor = Color.parseColor("#22FF0000"); + + /** + * TagContainerLayout background color(default #11FF0000) + */ + private int mBackgroundColor = Color.parseColor("#11FF0000"); + + /** + * The container layout gravity(default left) + */ + private int mGravity = Gravity.LEFT; + + /** + * The max line count of TagContainerLayout + */ + private int mMaxLines = 0; + + /** + * The max length for TagView(default max length 23) + */ + private int mTagMaxLength = 23; + + /** + * TagView Border width(default 0.5dp) + */ + private float mTagBorderWidth = 0.5f; + + /** + * TagView Border radius(default 15.0dp) + */ + private float mTagBorderRadius = 15.0f; + + /** + * TagView Text size(default 14sp) + */ + private float mTagTextSize = 14; + + /** + * Text direction(support:TEXT_DIRECTION_RTL & TEXT_DIRECTION_LTR, default TEXT_DIRECTION_LTR) + */ + private int mTagTextDirection = View.TEXT_DIRECTION_LTR; + + /** + * Horizontal padding for TagView, include left & right padding(left & right padding are equal, default 10dp) + */ + private int mTagHorizontalPadding = 10; + + /** + * Vertical padding for TagView, include top & bottom padding(top & bottom padding are equal, default 8dp) + */ + private int mTagVerticalPadding = 8; + + /** + * TagView border color(default #88F44336) + */ + private int mTagBorderColor = Color.parseColor("#88F44336"); + + /** + * TagView background color(default #33F44336) + */ + private int mTagBackgroundColor = Color.parseColor("#33F44336"); + + /** + * Selected TagView background color(default #33FF7669) + */ + private int mSelectedTagBackgroundColor = Color.parseColor("#33FF7669"); + + /** + * TagView text color(default #FF666666) + */ + private int mTagTextColor = Color.parseColor("#FF666666"); + + /** + * TagView typeface + */ + private Typeface mTagTypeface = Typeface.DEFAULT; + + /** + * Whether TagView can clickable(default unclickable) + */ + private boolean isTagViewClickable; + + /** + * Whether TagView can selectable(default unselectable) + */ + private boolean isTagViewSelectable; + + /** + * Tags + */ + private List mTags; + + /** + * Default image for new tags + */ + private int mDefaultImageDrawableID = -1; + + /** + * Can drag TagView(default false) + */ + private boolean mDragEnable; + + /** + * TagView drag state(default STATE_IDLE) + */ + private int mTagViewState = ViewDragHelper.STATE_IDLE; + + /** + * The distance between baseline and descent(default 2.75dp) + */ + private float mTagBdDistance = 2.75f; + + /** + * OnTagClickListener for TagView + */ + private TagView.OnTagClickListener mOnTagClickListener; + + /** + * Whether to support 'letters show with RTL(eg: Android to diordnA)' style(default false) + */ + private boolean mTagSupportLettersRTL = false; + + private Paint mPaint; + + private RectF mRectF; + + private ViewDragHelper mViewDragHelper; + + private List mChildViews; + + private int[] mViewPos; + + /** + * View theme(default PURE_CYAN) + */ + private int mTheme = ColorFactory.PURE_CYAN; + + /** + * Default interval(dp) + */ + private static final float DEFAULT_INTERVAL = 5; + + /** + * Default tag min length + */ + private static final int TAG_MIN_LENGTH = 3; + + /** + * The ripple effect duration(In milliseconds, default 1000ms) + */ + private int mRippleDuration = 1000; + + /** + * The ripple effect color(default #EEEEEE) + */ + private int mRippleColor; + + /** + * The ripple effect color alpha(the value may between 0 - 255, default 128) + */ + private int mRippleAlpha = 128; + + /** + * Enable draw cross icon(default false) + */ + private boolean mEnableCross = false; + + /** + * The cross area width(your cross click area, default equal to the TagView's height) + */ + private float mCrossAreaWidth = 0.0f; + + /** + * The padding of the cross area(default 10dp) + */ + private float mCrossAreaPadding = 10.0f; + + /** + * The cross icon color(default Color.BLACK) + */ + private int mCrossColor = Color.BLACK; + + /** + * The cross line width(default 1dp) + */ + private float mCrossLineWidth = 1.0f; + + /** + * TagView background resource + */ + private int mTagBackgroundResource; + + public TagContainerLayout(Context context) { + this(context, null); + } + + public TagContainerLayout(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public TagContainerLayout(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + init(context, attrs, defStyleAttr); + } + + private void init(Context context, AttributeSet attrs, int defStyleAttr) { + TypedArray attributes = context.obtainStyledAttributes(attrs, R.styleable.AndroidTagView, + defStyleAttr, 0); + mVerticalInterval = (int) attributes.getDimension(R.styleable.AndroidTagView_vertical_interval, + dp2px(context, DEFAULT_INTERVAL)); + mHorizontalInterval = (int) attributes.getDimension(R.styleable.AndroidTagView_horizontal_interval, + dp2px(context, DEFAULT_INTERVAL)); + mBorderWidth = attributes.getDimension(R.styleable.AndroidTagView_container_border_width, + dp2px(context, mBorderWidth)); + mBorderRadius = attributes.getDimension(R.styleable.AndroidTagView_container_border_radius, + dp2px(context, mBorderRadius)); + mTagBdDistance = attributes.getDimension(R.styleable.AndroidTagView_tag_bd_distance, + dp2px(context, mTagBdDistance)); + mBorderColor = attributes.getColor(R.styleable.AndroidTagView_container_border_color, + mBorderColor); + mBackgroundColor = attributes.getColor(R.styleable.AndroidTagView_container_background_color, + mBackgroundColor); + mDragEnable = attributes.getBoolean(R.styleable.AndroidTagView_container_enable_drag, false); + mSensitivity = attributes.getFloat(R.styleable.AndroidTagView_container_drag_sensitivity, + mSensitivity); + mGravity = attributes.getInt(R.styleable.AndroidTagView_container_gravity, mGravity); + mMaxLines = attributes.getInt(R.styleable.AndroidTagView_container_max_lines, mMaxLines); + mTagMaxLength = attributes.getInt(R.styleable.AndroidTagView_tag_max_length, mTagMaxLength); + mTheme = attributes.getInt(R.styleable.AndroidTagView_tag_theme, mTheme); + mTagBorderWidth = attributes.getDimension(R.styleable.AndroidTagView_tag_border_width, + dp2px(context, mTagBorderWidth)); + mTagBorderRadius = attributes.getDimension( + R.styleable.AndroidTagView_tag_corner_radius, dp2px(context, mTagBorderRadius)); + mTagHorizontalPadding = (int) attributes.getDimension( + R.styleable.AndroidTagView_tag_horizontal_padding, + dp2px(context, mTagHorizontalPadding)); + mTagVerticalPadding = (int) attributes.getDimension( + R.styleable.AndroidTagView_tag_vertical_padding, dp2px(context, mTagVerticalPadding)); + mTagTextSize = attributes.getDimension(R.styleable.AndroidTagView_tag_text_size, + sp2px(context, mTagTextSize)); + mTagBorderColor = attributes.getColor(R.styleable.AndroidTagView_tag_border_color, + mTagBorderColor); + mTagBackgroundColor = attributes.getColor(R.styleable.AndroidTagView_tag_background_color, + mTagBackgroundColor); + mTagTextColor = attributes.getColor(R.styleable.AndroidTagView_tag_text_color, mTagTextColor); + mTagTextDirection = attributes.getInt(R.styleable.AndroidTagView_tag_text_direction, mTagTextDirection); + isTagViewClickable = attributes.getBoolean(R.styleable.AndroidTagView_tag_clickable, false); + isTagViewSelectable = attributes.getBoolean(R.styleable.AndroidTagView_tag_selectable, false); + mRippleColor = attributes.getColor(R.styleable.AndroidTagView_tag_ripple_color, Color.parseColor("#EEEEEE")); + mRippleAlpha = attributes.getInteger(R.styleable.AndroidTagView_tag_ripple_alpha, mRippleAlpha); + mRippleDuration = attributes.getInteger(R.styleable.AndroidTagView_tag_ripple_duration, mRippleDuration); + mEnableCross = attributes.getBoolean(R.styleable.AndroidTagView_tag_enable_cross, mEnableCross); + mCrossAreaWidth = attributes.getDimension(R.styleable.AndroidTagView_tag_cross_width, + dp2px(context, mCrossAreaWidth)); + mCrossAreaPadding = attributes.getDimension(R.styleable.AndroidTagView_tag_cross_area_padding, + dp2px(context, mCrossAreaPadding)); + mCrossColor = attributes.getColor(R.styleable.AndroidTagView_tag_cross_color, mCrossColor); + mCrossLineWidth = attributes.getDimension(R.styleable.AndroidTagView_tag_cross_line_width, + dp2px(context, mCrossLineWidth)); + mTagSupportLettersRTL = attributes.getBoolean(R.styleable.AndroidTagView_tag_support_letters_rlt, + mTagSupportLettersRTL); + mTagBackgroundResource = attributes.getResourceId(R.styleable.AndroidTagView_tag_background, + mTagBackgroundResource); + attributes.recycle(); + + mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + mRectF = new RectF(); + mChildViews = new ArrayList(); + mViewDragHelper = ViewDragHelper.create(this, mSensitivity, new DragHelperCallBack()); + setWillNotDraw(false); + setTagMaxLength(mTagMaxLength); + setTagHorizontalPadding(mTagHorizontalPadding); + setTagVerticalPadding(mTagVerticalPadding); + + if (isInEditMode()) { + addTag("sample tag"); + } + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + + measureChildren(widthMeasureSpec, heightMeasureSpec); + final int childCount = getChildCount(); + int lines = childCount == 0 ? 0 : getChildLines(childCount); + int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec); +// int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec); + int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec); + int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec); + + if (childCount == 0) { + setMeasuredDimension(0, 0); + } else if (heightSpecMode == MeasureSpec.AT_MOST + || heightSpecMode == MeasureSpec.UNSPECIFIED) { + setMeasuredDimension(widthSpecSize, (mVerticalInterval + mChildHeight) * lines + - mVerticalInterval + getPaddingTop() + getPaddingBottom()); + } else { + setMeasuredDimension(widthSpecSize, heightSpecSize); + } + } + + @Override + protected void onSizeChanged(int w, int h, int oldw, int oldh) { + super.onSizeChanged(w, h, oldw, oldh); + mRectF.set(0, 0, w, h); + } + + @Override + protected void onLayout(boolean changed, int l, int t, int r, int b) { + int childCount; + if ((childCount = getChildCount()) <= 0) { + return; + } + int availableW = getMeasuredWidth() - getPaddingLeft() - getPaddingRight(); + int curRight = getMeasuredWidth() - getPaddingRight(); + int curTop = getPaddingTop(); + int curLeft = getPaddingLeft(); + int sPos = 0; + mViewPos = new int[childCount * 2]; + + for (int i = 0; i < childCount; i++) { + final View childView = getChildAt(i); + if (childView.getVisibility() != GONE) { + int width = childView.getMeasuredWidth(); + if (mGravity == Gravity.RIGHT) { + if (curRight - width < getPaddingLeft()) { + curRight = getMeasuredWidth() - getPaddingRight(); + curTop += mChildHeight + mVerticalInterval; + } + mViewPos[i * 2] = curRight - width; + mViewPos[i * 2 + 1] = curTop; + curRight -= width + mHorizontalInterval; + } else if (mGravity == Gravity.CENTER) { + if (curLeft + width - getPaddingLeft() > availableW) { + int leftW = getMeasuredWidth() - mViewPos[(i - 1) * 2] + - getChildAt(i - 1).getMeasuredWidth() - getPaddingRight(); + for (int j = sPos; j < i; j++) { + mViewPos[j * 2] = mViewPos[j * 2] + leftW / 2; + } + sPos = i; + curLeft = getPaddingLeft(); + curTop += mChildHeight + mVerticalInterval; + } + mViewPos[i * 2] = curLeft; + mViewPos[i * 2 + 1] = curTop; + curLeft += width + mHorizontalInterval; + + if (i == childCount - 1) { + int leftW = getMeasuredWidth() - mViewPos[i * 2] + - childView.getMeasuredWidth() - getPaddingRight(); + for (int j = sPos; j < childCount; j++) { + mViewPos[j * 2] = mViewPos[j * 2] + leftW / 2; + } + } + } else { + if (curLeft + width - getPaddingLeft() > availableW) { + curLeft = getPaddingLeft(); + curTop += mChildHeight + mVerticalInterval; + } + mViewPos[i * 2] = curLeft; + mViewPos[i * 2 + 1] = curTop; + curLeft += width + mHorizontalInterval; + } + } + } + + // layout all child views + for (int i = 0; i < mViewPos.length / 2; i++) { + View childView = getChildAt(i); + childView.layout(mViewPos[i * 2], mViewPos[i * 2 + 1], + mViewPos[i * 2] + childView.getMeasuredWidth(), + mViewPos[i * 2 + 1] + mChildHeight); + } + } + + @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + + mPaint.setStyle(Paint.Style.FILL); + mPaint.setColor(mBackgroundColor); + canvas.drawRoundRect(mRectF, mBorderRadius, mBorderRadius, mPaint); + + mPaint.setStyle(Paint.Style.STROKE); + mPaint.setStrokeWidth(mBorderWidth); + mPaint.setColor(mBorderColor); + canvas.drawRoundRect(mRectF, mBorderRadius, mBorderRadius, mPaint); + } + + @Override + public boolean onInterceptTouchEvent(MotionEvent ev) { + return mViewDragHelper.shouldInterceptTouchEvent(ev); + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + mViewDragHelper.processTouchEvent(event); + return true; + } + + @Override + public void computeScroll() { + super.computeScroll(); + if (mViewDragHelper.continueSettling(true)) { + requestLayout(); + } + } + + private int getChildLines(int childCount) { + int availableW = getMeasuredWidth() - getPaddingLeft() - getPaddingRight(); + int lines = 1; + for (int i = 0, curLineW = 0; i < childCount; i++) { + View childView = getChildAt(i); + int dis = childView.getMeasuredWidth() + mHorizontalInterval; + int height = childView.getMeasuredHeight(); + mChildHeight = i == 0 ? height : Math.min(mChildHeight, height); + curLineW += dis; + if (curLineW - mHorizontalInterval > availableW) { + lines++; + curLineW = dis; + } + } + + return mMaxLines <= 0 ? lines : mMaxLines; + } + + private int[] onUpdateColorFactory() { + int[] colors; + if (mTheme == ColorFactory.RANDOM) { + colors = ColorFactory.onRandomBuild(); + } else if (mTheme == ColorFactory.PURE_TEAL) { + colors = ColorFactory.onPureBuild(ColorFactory.PURE_COLOR.TEAL); + } else if (mTheme == ColorFactory.PURE_CYAN) { + colors = ColorFactory.onPureBuild(ColorFactory.PURE_COLOR.CYAN); + } else { + colors = new int[]{mTagBackgroundColor, mTagBorderColor, mTagTextColor, mSelectedTagBackgroundColor}; + } + return colors; + } + + private void onSetTag() { + if (mTags == null) { + throw new RuntimeException("NullPointer exception!"); + } + removeAllTags(); + if (mTags.size() == 0) { + return; + } + for (int i = 0; i < mTags.size(); i++) { + onAddTag(mTags.get(i), mChildViews.size()); + } + postInvalidate(); + } + + private void onAddTag(String text, int position) { + if (position < 0 || position > mChildViews.size()) { + throw new RuntimeException("Illegal position!"); + } + TagView tagView; + if (mDefaultImageDrawableID != -1) { + tagView = new TagView(getContext(), text, mDefaultImageDrawableID); + } else { + tagView = new TagView(getContext(), text); + } + initTagView(tagView, position); + mChildViews.add(position, tagView); + if (position < mChildViews.size()) { + for (int i = position; i < mChildViews.size(); i++) { + mChildViews.get(i).setTag(i); + } + } else { + tagView.setTag(position); + } + addView(tagView, position); + } + + private void initTagView(TagView tagView, int position) { + int[] colors; + if (mColorArrayList != null && mColorArrayList.size() > 0) { + if (mColorArrayList.size() == mTags.size() && + mColorArrayList.get(position).length >= 4) { + colors = mColorArrayList.get(position); + } else { + throw new RuntimeException("Illegal color list!"); + } + } else { + colors = onUpdateColorFactory(); + } + + tagView.setTagBackgroundColor(colors[0]); + tagView.setTagBorderColor(colors[1]); + tagView.setTagTextColor(colors[2]); + tagView.setTagSelectedBackgroundColor(colors[3]); + tagView.setTagMaxLength(mTagMaxLength); + tagView.setTextDirection(mTagTextDirection); + tagView.setTypeface(mTagTypeface); + tagView.setBorderWidth(mTagBorderWidth); + tagView.setBorderRadius(mTagBorderRadius); + tagView.setTextSize(mTagTextSize); + tagView.setHorizontalPadding(mTagHorizontalPadding); + tagView.setVerticalPadding(mTagVerticalPadding); + tagView.setIsViewClickable(isTagViewClickable); + tagView.setIsViewSelectable(isTagViewSelectable); + tagView.setBdDistance(mTagBdDistance); + tagView.setOnTagClickListener(mOnTagClickListener); + tagView.setRippleAlpha(mRippleAlpha); + tagView.setRippleColor(mRippleColor); + tagView.setRippleDuration(mRippleDuration); + tagView.setEnableCross(mEnableCross); + tagView.setCrossAreaWidth(mCrossAreaWidth); + tagView.setCrossAreaPadding(mCrossAreaPadding); + tagView.setCrossColor(mCrossColor); + tagView.setCrossLineWidth(mCrossLineWidth); + tagView.setTagSupportLettersRTL(mTagSupportLettersRTL); + tagView.setBackgroundResource(mTagBackgroundResource); + } + + private void invalidateTags() { + for (View view : mChildViews) { + final TagView tagView = (TagView) view; + tagView.setOnTagClickListener(mOnTagClickListener); + } + } + + private void onRemoveTag(int position) { + if (position < 0 || position >= mChildViews.size()) { + throw new RuntimeException("Illegal position!"); + } + mChildViews.remove(position); + removeViewAt(position); + for (int i = position; i < mChildViews.size(); i++) { + mChildViews.get(i).setTag(i); + } + // TODO, make removed view null? + } + + private void onRemoveConsecutiveTags(List positions) { + int smallestPosition = Collections.min(positions); + for (int position : positions) { + if (position < 0 || position >= mChildViews.size()) { + throw new RuntimeException("Illegal position!"); + } + mChildViews.remove(smallestPosition); + removeViewAt(smallestPosition); + } + for (int i = smallestPosition; i < mChildViews.size(); i++) { + mChildViews.get(i).setTag(i); + } + // TODO, make removed view null? + } + + private int[] onGetNewPosition(View view) { + int left = view.getLeft(); + int top = view.getTop(); + int bestMatchLeft = mViewPos[(int) view.getTag() * 2]; + int bestMatchTop = mViewPos[(int) view.getTag() * 2 + 1]; + int tmpTopDis = Math.abs(top - bestMatchTop); + for (int i = 0; i < mViewPos.length / 2; i++) { + if (Math.abs(top - mViewPos[i * 2 + 1]) < tmpTopDis) { + bestMatchTop = mViewPos[i * 2 + 1]; + tmpTopDis = Math.abs(top - mViewPos[i * 2 + 1]); + } + } + int rowChildCount = 0; + int tmpLeftDis = 0; + for (int i = 0; i < mViewPos.length / 2; i++) { + if (mViewPos[i * 2 + 1] == bestMatchTop) { + if (rowChildCount == 0) { + bestMatchLeft = mViewPos[i * 2]; + tmpLeftDis = Math.abs(left - bestMatchLeft); + } else { + if (Math.abs(left - mViewPos[i * 2]) < tmpLeftDis) { + bestMatchLeft = mViewPos[i * 2]; + tmpLeftDis = Math.abs(left - bestMatchLeft); + } + } + rowChildCount++; + } + } + return new int[]{bestMatchLeft, bestMatchTop}; + } + + private int onGetCoordinateReferPos(int left, int top) { + int pos = 0; + for (int i = 0; i < mViewPos.length / 2; i++) { + if (left == mViewPos[i * 2] && top == mViewPos[i * 2 + 1]) { + pos = i; + } + } + return pos; + } + + private void onChangeView(View view, int newPos, int originPos) { + mChildViews.remove(originPos); + mChildViews.add(newPos, view); + for (View child : mChildViews) { + child.setTag(mChildViews.indexOf(child)); + } + + removeViewAt(originPos); + addView(view, newPos); + } + + private int ceilTagBorderWidth() { + return (int) Math.ceil(mTagBorderWidth); + } + + private class DragHelperCallBack extends ViewDragHelper.Callback { + + @Override + public void onViewDragStateChanged(int state) { + super.onViewDragStateChanged(state); + mTagViewState = state; + } + + @Override + public boolean tryCaptureView(View child, int pointerId) { + requestDisallowInterceptTouchEvent(true); + return mDragEnable; + } + + @Override + public int clampViewPositionHorizontal(View child, int left, int dx) { + final int leftX = getPaddingLeft(); + final int rightX = getWidth() - child.getWidth() - getPaddingRight(); + return Math.min(Math.max(left, leftX), rightX); + } + + @Override + public int clampViewPositionVertical(View child, int top, int dy) { + final int topY = getPaddingTop(); + final int bottomY = getHeight() - child.getHeight() - getPaddingBottom(); + return Math.min(Math.max(top, topY), bottomY); + } + + @Override + public int getViewHorizontalDragRange(View child) { + return getMeasuredWidth() - child.getMeasuredWidth(); + } + + @Override + public int getViewVerticalDragRange(View child) { + return getMeasuredHeight() - child.getMeasuredHeight(); + } + + @Override + public void onViewReleased(View releasedChild, float xvel, float yvel) { + super.onViewReleased(releasedChild, xvel, yvel); + requestDisallowInterceptTouchEvent(false); + int[] pos = onGetNewPosition(releasedChild); + int posRefer = onGetCoordinateReferPos(pos[0], pos[1]); + onChangeView(releasedChild, posRefer, (int) releasedChild.getTag()); + mViewDragHelper.settleCapturedViewAt(pos[0], pos[1]); + invalidate(); + } + } + + /** + * Get current drag view state. + * + * @return + */ + public int getTagViewState() { + return mTagViewState; + } + + /** + * Get TagView text baseline and descent distance. + * + * @return + */ + public float getTagBdDistance() { + return mTagBdDistance; + } + + /** + * Set TagView text baseline and descent distance. + * + * @param tagBdDistance + */ + public void setTagBdDistance(float tagBdDistance) { + this.mTagBdDistance = dp2px(getContext(), tagBdDistance); + } + + /** + * Set tags + * + * @param tags + */ + public void setTags(List tags) { + mTags = tags; + onSetTag(); + } + + /** + * Set tags with own color + * + * @param tags + * @param colorArrayList + */ + public void setTags(List tags, List colorArrayList) { + mTags = tags; + mColorArrayList = colorArrayList; + onSetTag(); + } + + /** + * Set tags + * + * @param tags + */ + public void setTags(String... tags) { + mTags = Arrays.asList(tags); + onSetTag(); + } + + /** + * Inserts the specified TagView into this ContainerLayout at the end. + * + * @param text + */ + public void addTag(String text) { + addTag(text, mChildViews.size()); + } + + /** + * Inserts the specified TagView into this ContainerLayout at the specified location. + * The TagView is inserted before the current element at the specified location. + * + * @param text + * @param position + */ + public void addTag(String text, int position) { + onAddTag(text, position); + postInvalidate(); + } + + /** + * Remove a TagView in specified position. + * + * @param position + */ + public void removeTag(int position) { + onRemoveTag(position); + postInvalidate(); + } + + /** + * Remove TagView in multiple consecutive positions. + * + * + */ + public void removeConsecutiveTags(List positions) { + onRemoveConsecutiveTags(positions); + postInvalidate(); + } + + /** + * Remove all TagViews. + */ + public void removeAllTags() { + mChildViews.clear(); + removeAllViews(); + postInvalidate(); + } + + /** + * Set OnTagClickListener for TagView. + * + * @param listener + */ + public void setOnTagClickListener(TagView.OnTagClickListener listener) { + mOnTagClickListener = listener; + invalidateTags(); + } + + /** + * Toggle select a tag + * + * @param position + */ + public void toggleSelectTagView(int position) { + if (isTagViewSelectable){ + TagView tagView = ((TagView)mChildViews.get(position)); + if (tagView.getIsViewSelected()){ + tagView.deselectView(); + } else { + tagView.selectView(); + } + } + } + + /** + * Select a tag + * + * @param position + */ + public void selectTagView(int position) { + if (isTagViewSelectable) + ((TagView)mChildViews.get(position)).selectView(); + } + + /** + * Deselect a tag + * + * @param position + */ + public void deselectTagView(int position) { + if (isTagViewSelectable) + ((TagView)mChildViews.get(position)).deselectView(); + } + + /** + * Return selected TagView positions + * + * @return list of selected positions + */ + public List getSelectedTagViewPositions() { + List selectedPositions = new ArrayList<>(); + for (int i = 0; i < mChildViews.size(); i++){ + if (((TagView)mChildViews.get(i)).getIsViewSelected()){ + selectedPositions.add(i); + } + } + return selectedPositions; + } + + /** + * Return selected TagView text + * + * @return list of selected tag text + */ + public List getSelectedTagViewText() { + List selectedTagText = new ArrayList<>(); + for (int i = 0; i < mChildViews.size(); i++){ + TagView tagView = (TagView)mChildViews.get(i); + if ((tagView.getIsViewSelected())){ + selectedTagText.add(tagView.getText()); + } + } + return selectedTagText; + } + + /** + * Return number of child tags + * + * @return size + */ + public int size() { + return mChildViews.size(); + } + + /** + * Get TagView text. + * + * @param position + * @return + */ + public String getTagText(int position) { + return ((TagView) mChildViews.get(position)).getText(); + } + + /** + * Get a string list for all tags in TagContainerLayout. + * + * @return + */ + public List getTags() { + List tmpList = new ArrayList(); + for (View view : mChildViews) { + if (view instanceof TagView) { + tmpList.add(((TagView) view).getText()); + } + } + return tmpList; + } + + /** + * Set whether the child view can be dragged. + * + * @param enable + */ + public void setDragEnable(boolean enable) { + this.mDragEnable = enable; + } + + /** + * Get current view is drag enable attribute. + * + * @return + */ + public boolean getDragEnable() { + return mDragEnable; + } + + /** + * Set vertical interval + * + * @param interval + */ + public void setVerticalInterval(float interval) { + mVerticalInterval = (int) dp2px(getContext(), interval); + postInvalidate(); + } + + /** + * Get vertical interval in this view. + * + * @return + */ + public int getVerticalInterval() { + return mVerticalInterval; + } + + /** + * Set horizontal interval. + * + * @param interval + */ + public void setHorizontalInterval(float interval) { + mHorizontalInterval = (int) dp2px(getContext(), interval); + postInvalidate(); + } + + /** + * Get horizontal interval in this view. + * + * @return + */ + public int getHorizontalInterval() { + return mHorizontalInterval; + } + + /** + * Get TagContainerLayout border width. + * + * @return + */ + public float getBorderWidth() { + return mBorderWidth; + } + + /** + * Set TagContainerLayout border width. + * + * @param width + */ + public void setBorderWidth(float width) { + this.mBorderWidth = width; + } + + /** + * Get TagContainerLayout border radius. + * + * @return + */ + public float getBorderRadius() { + return mBorderRadius; + } + + /** + * Set TagContainerLayout border radius. + * + * @param radius + */ + public void setBorderRadius(float radius) { + this.mBorderRadius = radius; + } + + /** + * Get TagContainerLayout border color. + * + * @return + */ + public int getBorderColor() { + return mBorderColor; + } + + /** + * Set TagContainerLayout border color. + * + * @param color + */ + public void setBorderColor(int color) { + this.mBorderColor = color; + } + + /** + * Get TagContainerLayout background color. + * + * @return + */ + public int getBackgroundColor() { + return mBackgroundColor; + } + + /** + * Set TagContainerLayout background color. + * + * @param color + */ + public void setBackgroundColor(int color) { + this.mBackgroundColor = color; + } + + /** + * Get container layout gravity. + * + * @return + */ + public int getGravity() { + return mGravity; + } + + /** + * Set container layout gravity. + * + * @param gravity + */ + public void setGravity(int gravity) { + this.mGravity = gravity; + } + + /** + * Get TagContainerLayout ViewDragHelper sensitivity. + * + * @return + */ + public float getSensitivity() { + return mSensitivity; + } + + /** + * Set TagContainerLayout ViewDragHelper sensitivity. + * + * @param sensitivity + */ + public void setSensitivity(float sensitivity) { + this.mSensitivity = sensitivity; + } + + /** + * Get default tag image + * + * @return + */ + public int getDefaultImageDrawableID() { + return mDefaultImageDrawableID; + } + + /** + * Set default image for tags. + * + * @param imageID + */ + public void setDefaultImageDrawableID(int imageID) { + this.mDefaultImageDrawableID = imageID; + } + + /** + * Set max line count for TagContainerLayout + * + * @param maxLines max line count + */ + public void setMaxLines(int maxLines) { + mMaxLines = maxLines; + postInvalidate(); + } + + /** + * Get TagContainerLayout's max lines + * + * @return maxLines + */ + public int getMaxLines() { + return mMaxLines; + } + + /** + * Set the TagView text max length(must greater or equal to 3). + * + * @param maxLength + */ + public void setTagMaxLength(int maxLength) { + mTagMaxLength = maxLength < TAG_MIN_LENGTH ? TAG_MIN_LENGTH : maxLength; + } + + /** + * Get TagView max length. + * + * @return + */ + public int getTagMaxLength() { + return mTagMaxLength; + } + + /** + * Set TagView theme. + * + * @param theme + */ + public void setTheme(int theme) { + mTheme = theme; + } + + /** + * Get TagView theme. + * + * @return + */ + public int getTheme() { + return mTheme; + } + + /** + * Get TagView is clickable. + * + * @return + */ + public boolean getIsTagViewClickable() { + return isTagViewClickable; + } + + /** + * Set TagView is clickable + * + * @param clickable + */ + public void setIsTagViewClickable(boolean clickable) { + this.isTagViewClickable = clickable; + } + + /** + * Get TagView is selectable. + * + * @return + */ + public boolean getIsTagViewSelectable() { + return isTagViewSelectable; + } + + /** + * Set TagView is selectable + * + * @param selectable + */ + public void setIsTagViewSelectable(boolean selectable) { + this.isTagViewSelectable= selectable; + } + + /** + * Get TagView border width. + * + * @return + */ + public float getTagBorderWidth() { + return mTagBorderWidth; + } + + /** + * Set TagView border width. + * + * @param width + */ + public void setTagBorderWidth(float width) { + this.mTagBorderWidth = width; + } + + /** + * Get TagView border radius. + * + * @return + */ + public float getTagBorderRadius() { + return mTagBorderRadius; + } + + /** + * Set TagView border radius. + * + * @param radius + */ + public void setTagBorderRadius(float radius) { + this.mTagBorderRadius = radius; + } + + /** + * Get TagView text size. + * + * @return + */ + public float getTagTextSize() { + return mTagTextSize; + } + + /** + * Set TagView text size. + * + * @param size + */ + public void setTagTextSize(float size) { + this.mTagTextSize = size; + } + + /** + * Get TagView horizontal padding. + * + * @return + */ + public int getTagHorizontalPadding() { + return mTagHorizontalPadding; + } + + /** + * Set TagView horizontal padding. + * + * @param padding + */ + public void setTagHorizontalPadding(int padding) { + int ceilWidth = ceilTagBorderWidth(); + this.mTagHorizontalPadding = padding < ceilWidth ? ceilWidth : padding; + } + + /** + * Get TagView vertical padding. + * + * @return + */ + public int getTagVerticalPadding() { + return mTagVerticalPadding; + } + + /** + * Set TagView vertical padding. + * + * @param padding + */ + public void setTagVerticalPadding(int padding) { + int ceilWidth = ceilTagBorderWidth(); + this.mTagVerticalPadding = padding < ceilWidth ? ceilWidth : padding; + } + + /** + * Get TagView border color. + * + * @return + */ + public int getTagBorderColor() { + return mTagBorderColor; + } + + /** + * Set TagView border color. + * + * @param color + */ + public void setTagBorderColor(int color) { + this.mTagBorderColor = color; + } + + /** + * Get TagView background color. + * + * @return + */ + public int getTagBackgroundColor() { + return mTagBackgroundColor; + } + + /** + * Set TagView background color. + * + * @param color + */ + public void setTagBackgroundColor(int color) { + this.mTagBackgroundColor = color; + } + + /** + * Get TagView text color. + * + * @return + */ + public int getTagTextColor() { + return mTagTextColor; + } + + /** + * Set tag text direction, support:View.TEXT_DIRECTION_RTL and View.TEXT_DIRECTION_LTR, + * default View.TEXT_DIRECTION_LTR + * + * @param textDirection + */ + public void setTagTextDirection(int textDirection) { + this.mTagTextDirection = textDirection; + } + + /** + * Get TagView typeface. + * + * @return + */ + public Typeface getTagTypeface() { + return mTagTypeface; + } + + /** + * Set TagView typeface. + * + * @param typeface + */ + public void setTagTypeface(Typeface typeface) { + this.mTagTypeface = typeface; + } + + /** + * Get tag text direction + * + * @return + */ + public int getTagTextDirection() { + return mTagTextDirection; + } + + /** + * Set TagView text color. + * + * @param color + */ + public void setTagTextColor(int color) { + this.mTagTextColor = color; + } + + /** + * Get the ripple effect color's alpha. + * + * @return + */ + public int getRippleAlpha() { + return mRippleAlpha; + } + + /** + * Set TagView ripple effect alpha, the value may between 0 to 255, default is 128. + * + * @param mRippleAlpha + */ + public void setRippleAlpha(int mRippleAlpha) { + this.mRippleAlpha = mRippleAlpha; + } + + /** + * Get the ripple effect color. + * + * @return + */ + public int getRippleColor() { + return mRippleColor; + } + + /** + * Set TagView ripple effect color. + * + * @param mRippleColor + */ + public void setRippleColor(int mRippleColor) { + this.mRippleColor = mRippleColor; + } + + /** + * Get the ripple effect duration. + * + * @return + */ + public int getRippleDuration() { + return mRippleDuration; + } + + /** + * Set TagView ripple effect duration, default is 1000ms. + * + * @param mRippleDuration + */ + public void setRippleDuration(int mRippleDuration) { + this.mRippleDuration = mRippleDuration; + } + + /** + * Set TagView cross color. + * + * @return + */ + public int getCrossColor() { + return mCrossColor; + } + + /** + * Set TagView cross color, default Color.BLACK. + * + * @param mCrossColor + */ + public void setCrossColor(int mCrossColor) { + this.mCrossColor = mCrossColor; + } + + /** + * Get agView cross area's padding. + * + * @return + */ + public float getCrossAreaPadding() { + return mCrossAreaPadding; + } + + /** + * Set TagView cross area padding, default 10dp. + * + * @param mCrossAreaPadding + */ + public void setCrossAreaPadding(float mCrossAreaPadding) { + this.mCrossAreaPadding = mCrossAreaPadding; + } + + /** + * Get is the TagView's cross enable, default false. + * + * @return + */ + public boolean isEnableCross() { + return mEnableCross; + } + + /** + * Enable or disable the TagView's cross. + * + * @param mEnableCross + */ + public void setEnableCross(boolean mEnableCross) { + this.mEnableCross = mEnableCross; + } + + /** + * Get TagView cross area width. + * + * @return + */ + public float getCrossAreaWidth() { + return mCrossAreaWidth; + } + + /** + * Set TagView area width. + * + * @param mCrossAreaWidth + */ + public void setCrossAreaWidth(float mCrossAreaWidth) { + this.mCrossAreaWidth = mCrossAreaWidth; + } + + /** + * Get TagView cross line width. + * + * @return + */ + public float getCrossLineWidth() { + return mCrossLineWidth; + } + + /** + * Set TagView cross line width, default 1dp. + * + * @param mCrossLineWidth + */ + public void setCrossLineWidth(float mCrossLineWidth) { + this.mCrossLineWidth = mCrossLineWidth; + } + + /** + * Get the 'letters show with RTL(like: Android to diordnA)' style if it's enabled + * + * @return + */ + public boolean isTagSupportLettersRTL() { + return mTagSupportLettersRTL; + } + + /** + * Set whether the 'support letters show with RTL(like: Android to diordnA)' style is enabled. + * + * @param mTagSupportLettersRTL + */ + public void setTagSupportLettersRTL(boolean mTagSupportLettersRTL) { + this.mTagSupportLettersRTL = mTagSupportLettersRTL; + } + + /** + * Get TagView in specified position. + * + * @param position the position of the TagView + * @return + */ + public TagView getTagView(int position){ + if (position < 0 || position >= mChildViews.size()) { + throw new RuntimeException("Illegal position!"); + } + return (TagView) mChildViews.get(position); + } + + /** + * Get TagView background resource + * @return + */ + public int getTagBackgroundResource() { + return mTagBackgroundResource; + } + + /** + * Set TagView background resource + * @param tagBackgroundResource + */ + public void setTagBackgroundResource(@DrawableRes int tagBackgroundResource) { + this.mTagBackgroundResource = tagBackgroundResource; + } +} diff --git a/androidtagview/src/main/java/co/lujun/androidtagview/TagView.java b/androidtagview/src/main/java/co/lujun/androidtagview/TagView.java index 23d7e4d..1a93975 100644 --- a/androidtagview/src/main/java/co/lujun/androidtagview/TagView.java +++ b/androidtagview/src/main/java/co/lujun/androidtagview/TagView.java @@ -1,15 +1,43 @@ +/* + * Copyright 2015 lujun + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package co.lujun.androidtagview; +import android.animation.ValueAnimator; +import android.annotation.TargetApi; import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.BitmapShader; +import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.graphics.Paint; -import android.graphics.Rect; +import android.graphics.Path; import android.graphics.RectF; -import android.support.v4.widget.ViewDragHelper; +import android.graphics.Region; +import android.graphics.Shader; +import android.graphics.Typeface; +import android.os.Build; import android.text.TextUtils; import android.view.MotionEvent; import android.view.View; +import androidx.customview.widget.ViewDragHelper; + +import static co.lujun.androidtagview.Utils.dp2px; + /** * Author: lujun(http://blog.lujun.co) * Date: 2015-12-31 11:47 @@ -37,29 +65,48 @@ public class TagView extends View { /** TagView background color*/ private int mBackgroundColor; + /** TagView background color*/ + private int mSelectedBackgroundColor; + /** TagView text color*/ private int mTextColor; /** Whether this view clickable*/ private boolean isViewClickable; + /** Whether this view selectable*/ + private boolean isViewSelectable; + + /** Whether this view selected*/ + private boolean isViewSelected; + /** The max length for this tag view*/ private int mTagMaxLength; /** OnTagClickListener for click action*/ private OnTagClickListener mOnTagClickListener; - /** Move slop(default 20px)*/ - private int mMoveSlop = 20; + /** Move slop(default 5dp)*/ + private int mMoveSlop = 5; + + /** Scroll slop threshold 4dp*/ + private int mSlopThreshold = 4; /** How long trigger long click callback(default 500ms)*/ private int mLongPressTime = 500; - private Paint mPaint; + /** Text direction(support:TEXT_DIRECTION_RTL & TEXT_DIRECTION_LTR, default TEXT_DIRECTION_LTR)*/ + private int mTextDirection = View.TEXT_DIRECTION_LTR; - private RectF mRectF; + /** The distance between baseline and descent*/ + private float bdDistance; + + /** Whether to support 'letters show with RTL(eg: Android to diordnA)' style(default false)*/ + private boolean mTagSupportLettersRTL = false; - private Rect mTextBound; + private Paint mPaint, mRipplePaint; + + private RectF mRectF; private String mAbstractText, mOriginText; @@ -67,74 +114,205 @@ public class TagView extends View { private int mLastX, mLastY; + private float fontH, fontW; + + private float mTouchX, mTouchY; + + /** The ripple effect duration(default 1000ms)*/ + private int mRippleDuration = 1000; + + private float mRippleRadius; + + private int mRippleColor; + + private int mRippleAlpha; + + private Path mPath; + + private Typeface mTypeface; + + private ValueAnimator mRippleValueAnimator; + + private Bitmap mBitmapImage; + + private boolean mEnableCross; + + private float mCrossAreaWidth; + + private float mCrossAreaPadding; + + private int mCrossColor; + + private float mCrossLineWidth; + + private boolean unSupportedClipPath = false; + private Runnable mLongClickHandle = new Runnable() { @Override public void run() { - int state = ((TagContainerLayout)getParent()).getTagViewState(); - if (!isMoved && !isUp && state == ViewDragHelper.STATE_IDLE){ - isExecLongClick = true; - mOnTagClickListener.onTagLongClick((int) getTag(), getText()); + if (!isMoved && !isUp){ + int state = ((TagContainerLayout)getParent()).getTagViewState(); + if (state == ViewDragHelper.STATE_IDLE){ + isExecLongClick = true; + mOnTagClickListener.onTagLongClick((int) getTag(), getText()); + } } } }; public TagView(Context context, String text){ super(context); - init(text); + init(context, text); + } + + public TagView(Context context, String text, int defaultImageID){ + super(context); + init(context, text); + mBitmapImage = BitmapFactory.decodeResource(getResources(), defaultImageID); } - private void init(String text){ + private void init(Context context, String text){ mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + mRipplePaint = new Paint(Paint.ANTI_ALIAS_FLAG); + mRipplePaint.setStyle(Paint.Style.FILL); mRectF = new RectF(); - mTextBound = new Rect(); - mOriginText = text; + mPath = new Path(); + mOriginText = text == null ? "" : text; + mMoveSlop = (int) dp2px(context, mMoveSlop); + mSlopThreshold = (int) dp2px(context, mSlopThreshold); } private void onDealText(){ if(!TextUtils.isEmpty(mOriginText)) { mAbstractText = mOriginText.length() <= mTagMaxLength ? mOriginText : mOriginText.substring(0, mTagMaxLength - 3) + "..."; + }else { + mAbstractText = ""; } + mPaint.setTypeface(mTypeface); mPaint.setTextSize(mTextSize); - mPaint.getTextBounds(mAbstractText, 0, mAbstractText.length(), mTextBound); + final Paint.FontMetrics fontMetrics = mPaint.getFontMetrics(); + fontH = fontMetrics.descent - fontMetrics.ascent; + if (mTextDirection == View.TEXT_DIRECTION_RTL){ + fontW = 0; + for (char c : mAbstractText.toCharArray()) { + String sc = String.valueOf(c); + fontW += mPaint.measureText(sc); + } + }else { + fontW = mPaint.measureText(mAbstractText); + } } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); - setMeasuredDimension(mHorizontalPadding * 2 + mTextBound.width(), - mVerticalPadding * 2 + mTextBound.height()); + int height = mVerticalPadding * 2 + (int) fontH; + int width = mHorizontalPadding * 2 + (int) fontW + (isEnableCross() ? height : 0) + (isEnableImage() ? height : 0); + mCrossAreaWidth = Math.min(Math.max(mCrossAreaWidth, height), width); + setMeasuredDimension(width, height); } @Override - protected void onDraw(Canvas canvas) { - super.onDraw(canvas); - mRectF.set(canvas.getClipBounds().left + mBorderWidth, - canvas.getClipBounds().top + mBorderWidth, - canvas.getClipBounds().right - mBorderWidth, - canvas.getClipBounds().bottom - mBorderWidth); + protected void onSizeChanged(int w, int h, int oldw, int oldh) { + super.onSizeChanged(w, h, oldw, oldh); + mRectF.set(mBorderWidth, mBorderWidth, w - mBorderWidth, h - mBorderWidth); + } + @Override + protected void onDraw(Canvas canvas) { + // draw background mPaint.setStyle(Paint.Style.FILL); - mPaint.setColor(mBackgroundColor); + mPaint.setColor(getIsViewSelected() ? mSelectedBackgroundColor : mBackgroundColor); canvas.drawRoundRect(mRectF, mBorderRadius, mBorderRadius, mPaint); + // draw border mPaint.setStyle(Paint.Style.STROKE); mPaint.setStrokeWidth(mBorderWidth); mPaint.setColor(mBorderColor); canvas.drawRoundRect(mRectF, mBorderRadius, mBorderRadius, mPaint); + // draw ripple for TagView + drawRipple(canvas); + + // draw text mPaint.setStyle(Paint.Style.FILL); mPaint.setColor(mTextColor); - canvas.drawText(mAbstractText, getWidth() / 2 - mTextBound.width() / 2, - getHeight() / 2 + mTextBound.height() / 2, mPaint); + + if (mTextDirection == View.TEXT_DIRECTION_RTL) { + if (mTagSupportLettersRTL){ + float tmpX = (isEnableCross() ? getWidth() + getHeight() : getWidth()) / 2 + + fontW / 2; + for (char c : mAbstractText.toCharArray()) { + String sc = String.valueOf(c); + tmpX -= mPaint.measureText(sc); + canvas.drawText(sc, tmpX, getHeight() / 2 + fontH / 2 - bdDistance, mPaint); + } + }else { + canvas.drawText(mAbstractText, + (isEnableCross() ? getWidth() + fontW : getWidth()) / 2 - fontW / 2, + getHeight() / 2 + fontH / 2 - bdDistance, mPaint); + } + } else { + canvas.drawText(mAbstractText, + (isEnableCross() ? getWidth() - getHeight() : getWidth()) / 2 - fontW / 2 + (isEnableImage() ? getHeight() / 2 : 0), + getHeight() / 2 + fontH / 2 - bdDistance, mPaint); + } + + // draw cross + drawCross(canvas); + + // draw image + drawImage(canvas); + } + + @Override + public boolean dispatchTouchEvent(MotionEvent event) { + if (isViewClickable){ + int y = (int) event.getY(); + int x = (int) event.getX(); + int action = event.getAction(); + switch (action){ + case MotionEvent.ACTION_DOWN: + if (getParent() != null) { + getParent().requestDisallowInterceptTouchEvent(true); + } + mLastY = y; + mLastX = x; + break; + + case MotionEvent.ACTION_MOVE: + if (!isViewSelected && (Math.abs(mLastY - y) > mSlopThreshold + || Math.abs(mLastX - x) > mSlopThreshold)){ + if (getParent() != null) { + getParent().requestDisallowInterceptTouchEvent(false); + } + isMoved = true; + return false; + } + break; + } + } + return super.dispatchTouchEvent(event); } @Override public boolean onTouchEvent(MotionEvent event) { - if (isViewClickable && mOnTagClickListener != null){ + int action = event.getAction(); + if (action == MotionEvent.ACTION_DOWN) { + mRippleRadius = 0.0f; + mTouchX = event.getX(); + mTouchY = event.getY(); + splashRipple(); + } + if (isEnableCross() && isClickCrossArea(event) && mOnTagClickListener != null){ + if (action == MotionEvent.ACTION_UP) { + mOnTagClickListener.onTagCrossClick((int) getTag()); + } + return true; + }else if (isViewClickable && mOnTagClickListener != null){ int x = (int) event.getX(); int y = (int) event.getY(); - int action = event.getAction(); switch (action){ case MotionEvent.ACTION_DOWN: mLastY = y; @@ -151,12 +329,15 @@ public boolean onTouchEvent(MotionEvent event) { } if (Math.abs(mLastX - x) > mMoveSlop || Math.abs(mLastY - y) > mMoveSlop){ isMoved = true; + if (isViewSelected){ + mOnTagClickListener.onSelectedTagDrag((int) getTag(), getText()); + } } break; case MotionEvent.ACTION_UP: isUp = true; - if (!isExecLongClick) { + if (!isExecLongClick && !isMoved) { mOnTagClickListener.onTagClick((int) getTag(), getText()); } break; @@ -166,6 +347,107 @@ public boolean onTouchEvent(MotionEvent event) { return super.onTouchEvent(event); } + private boolean isClickCrossArea(MotionEvent event){ + if (mTextDirection == View.TEXT_DIRECTION_RTL){ + return event.getX() <= mCrossAreaWidth; + } + return event.getX() >= getWidth() - mCrossAreaWidth; + } + + private void drawImage(Canvas canvas){ + if (isEnableImage()) { + Bitmap scaledImageBitmap = Bitmap.createScaledBitmap(mBitmapImage, Math.round(getHeight() - mBorderWidth), Math.round(getHeight() - mBorderWidth), false); + + Paint paint = new Paint(); + paint.setAntiAlias(true); + paint.setShader(new BitmapShader(scaledImageBitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP)); + RectF rect = new RectF(mBorderWidth, mBorderWidth, getHeight() - mBorderWidth, getHeight() - mBorderWidth); + canvas.drawRoundRect(rect, rect.height()/2, rect.height()/2, paint); + } + } + + private void drawCross(Canvas canvas){ + if (isEnableCross()){ + mCrossAreaPadding = mCrossAreaPadding > getHeight() / 2 ? getHeight() / 2 : + mCrossAreaPadding; + int ltX, ltY, rbX, rbY, lbX, lbY, rtX, rtY; + ltX = mTextDirection == View.TEXT_DIRECTION_RTL ? (int)(mCrossAreaPadding) : + (int)(getWidth() - getHeight() + mCrossAreaPadding); + ltY = mTextDirection == View.TEXT_DIRECTION_RTL ? (int)(mCrossAreaPadding) : + (int)(mCrossAreaPadding); + lbX = mTextDirection == View.TEXT_DIRECTION_RTL ? (int)(mCrossAreaPadding) : + (int)(getWidth() - getHeight() + mCrossAreaPadding); + lbY = mTextDirection == View.TEXT_DIRECTION_RTL ? + (int)(getHeight() - mCrossAreaPadding) : (int)(getHeight() - mCrossAreaPadding); + rtX = mTextDirection == View.TEXT_DIRECTION_RTL ? + (int)(getHeight() - mCrossAreaPadding) : (int)(getWidth() - mCrossAreaPadding); + rtY = mTextDirection == View.TEXT_DIRECTION_RTL ? (int)(mCrossAreaPadding) : + (int)(mCrossAreaPadding); + rbX = mTextDirection == View.TEXT_DIRECTION_RTL ? + (int)(getHeight() - mCrossAreaPadding) : (int)(getWidth() - mCrossAreaPadding); + rbY = mTextDirection == View.TEXT_DIRECTION_RTL ? + (int)(getHeight() - mCrossAreaPadding) : (int)(getHeight() - mCrossAreaPadding); + + mPaint.setStyle(Paint.Style.STROKE); + mPaint.setColor(mCrossColor); + mPaint.setStrokeWidth(mCrossLineWidth); + canvas.drawLine(ltX, ltY, rbX, rbY, mPaint); + canvas.drawLine(lbX, lbY, rtX, rtY, mPaint); + } + } + + @TargetApi(Build.VERSION_CODES.HONEYCOMB) + private void drawRipple(Canvas canvas){ + if (isViewClickable && Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB && + canvas != null && !unSupportedClipPath){ + + // Disable hardware acceleration for 'Canvas.clipPath()' when running on API from 11 to 17 + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2){ + setLayerType(LAYER_TYPE_SOFTWARE, null); + } + try { + canvas.save(); + mPath.reset(); + + canvas.clipPath(mPath); + mPath.addRoundRect(mRectF, mBorderRadius, mBorderRadius, Path.Direction.CCW); + +// bug: https://github.com/whilu/AndroidTagView/issues/88 + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + canvas.clipPath(mPath); + } else { + canvas.clipPath(mPath, Region.Op.REPLACE); + } + + canvas.drawCircle(mTouchX, mTouchY, mRippleRadius, mRipplePaint); + canvas.restore(); + }catch (UnsupportedOperationException e){ + unSupportedClipPath = true; + } + } + } + + @TargetApi(Build.VERSION_CODES.HONEYCOMB) + private void splashRipple(){ + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB && mTouchX > 0 && mTouchY > 0){ + mRipplePaint.setColor(mRippleColor); + mRipplePaint.setAlpha(mRippleAlpha); + final float maxDis = Math.max(Math.max(Math.max(mTouchX, mTouchY), + Math.abs(getMeasuredWidth() - mTouchX)), Math.abs(getMeasuredHeight() - mTouchY)); + + mRippleValueAnimator = ValueAnimator.ofFloat(0.0f, maxDis).setDuration(mRippleDuration); + mRippleValueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + float animValue = (float) animation.getAnimatedValue(); + mRippleRadius = animValue >= maxDis ? 0 : animValue; + postInvalidate(); + } + }); + mRippleValueAnimator.start(); + } + } + public String getText(){ return mOriginText; } @@ -174,6 +456,10 @@ public boolean getIsViewClickable(){ return isViewClickable; } + public boolean getIsViewSelected(){ + return isViewSelected; + } + public void setTagMaxLength(int maxLength){ this.mTagMaxLength = maxLength; onDealText(); @@ -183,10 +469,22 @@ public void setOnTagClickListener(OnTagClickListener listener){ this.mOnTagClickListener = listener; } + public int getTagBackgroundColor(){ + return mBackgroundColor; + } + + public int getTagSelectedBackgroundColor(){ + return mSelectedBackgroundColor; + } + public void setTagBackgroundColor(int color){ this.mBackgroundColor = color; } + public void setTagSelectedBackgroundColor(int color){ + this.mSelectedBackgroundColor = color; + } + public void setTagBorderColor(int color){ this.mBorderColor = color; } @@ -220,8 +518,113 @@ public void setIsViewClickable(boolean clickable) { this.isViewClickable = clickable; } + public void setImage(Bitmap newImage) { + this.mBitmapImage = newImage; + this.invalidate(); + } + + public void setIsViewSelectable(boolean viewSelectable) { + isViewSelectable = viewSelectable; + } + + //TODO change background color + public void selectView() { + if (isViewSelectable && !getIsViewSelected()) { + this.isViewSelected = true; + postInvalidate(); + } + } + + public void deselectView() { + if (isViewSelectable && getIsViewSelected()) { + this.isViewSelected = false; + postInvalidate(); + } + } + public interface OnTagClickListener{ void onTagClick(int position, String text); void onTagLongClick(int position, String text); + void onSelectedTagDrag(int position, String text); + void onTagCrossClick(int position); + } + + public int getTextDirection() { + return mTextDirection; + } + + public void setTextDirection(int textDirection) { + this.mTextDirection = textDirection; + } + + public void setTypeface(Typeface typeface) { + this.mTypeface = typeface; + onDealText(); + } + + public void setRippleAlpha(int mRippleAlpha) { + this.mRippleAlpha = mRippleAlpha; + } + + public void setRippleColor(int mRippleColor) { + this.mRippleColor = mRippleColor; + } + + public void setRippleDuration(int mRippleDuration) { + this.mRippleDuration = mRippleDuration; + } + + public void setBdDistance(float bdDistance) { + this.bdDistance = bdDistance; + } + + public boolean isEnableImage() { return mBitmapImage != null && mTextDirection != View.TEXT_DIRECTION_RTL; } + + public boolean isEnableCross() { + return mEnableCross; + } + + public void setEnableCross(boolean mEnableCross) { + this.mEnableCross = mEnableCross; + } + + public float getCrossAreaWidth() { + return mCrossAreaWidth; + } + + public void setCrossAreaWidth(float mCrossAreaWidth) { + this.mCrossAreaWidth = mCrossAreaWidth; + } + + public float getCrossLineWidth() { + return mCrossLineWidth; + } + + public void setCrossLineWidth(float mCrossLineWidth) { + this.mCrossLineWidth = mCrossLineWidth; + } + + public float getCrossAreaPadding() { + return mCrossAreaPadding; + } + + public void setCrossAreaPadding(float mCrossAreaPadding) { + this.mCrossAreaPadding = mCrossAreaPadding; + } + + public int getCrossColor() { + return mCrossColor; + } + + public void setCrossColor(int mCrossColor) { + this.mCrossColor = mCrossColor; + } + + public boolean isTagSupportLettersRTL() { + return mTagSupportLettersRTL; + } + + public void setTagSupportLettersRTL(boolean mTagSupportLettersRTL) { + this.mTagSupportLettersRTL = mTagSupportLettersRTL; } } diff --git a/androidtagview/src/main/java/co/lujun/androidtagview/Utils.java b/androidtagview/src/main/java/co/lujun/androidtagview/Utils.java new file mode 100644 index 0000000..c2b68de --- /dev/null +++ b/androidtagview/src/main/java/co/lujun/androidtagview/Utils.java @@ -0,0 +1,65 @@ +/* + * Copyright 2015 lujun + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package co.lujun.androidtagview; + +import android.content.Context; +import android.graphics.Color; + +/** + * Author: lujun(http://blog.lujun.co) + * Date: 2016-12-7 21:53 + */ + +public class Utils { + + public static float dp2px(Context context, float dp) { + final float scale = context.getResources().getDisplayMetrics().density; + return dp * scale + 0.5f; + } + + public static float sp2px(Context context, float sp) { + final float scale = context.getResources().getDisplayMetrics().scaledDensity; + return sp * scale; + } + + /** + * If the color is Dark, make it lighter and vice versa + * + * @param color in int, + * @param factor The factor greater than 0.0 and smaller than 1.0 + * @return int + */ + public static int manipulateColorBrightness(int color, float factor) { + int a = Color.alpha(color); + int r = Color.red(color); + int g = Color.green(color); + int b = Color.blue(color); +// if (r + b + g < 128 * 3) factor = 1 / factor;// check if the color is bright or dark +// r = Math.round(r * factor); +// b = Math.round(b * factor); +// g = Math.round(g * factor); + if (r > 127) r = 255 - Math.round((255 - r) * factor); + if (g > 127) g = 255 - Math.round((255 - g) * factor); + if (b > 127) b = 255 - Math.round((255 - b) * factor); + + return Color.argb(a, + Math.min(r, 255), + Math.min(g, 255), + Math.min(b, 255) + ); + } +} diff --git a/androidtagview/src/main/res/values/attrs.xml b/androidtagview/src/main/res/values/attrs.xml index 3004a21..098f9a9 100644 --- a/androidtagview/src/main/res/values/attrs.xml +++ b/androidtagview/src/main/res/values/attrs.xml @@ -1,31 +1,55 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/androidtagview/src/main/res/values/strings.xml b/androidtagview/src/main/res/values/strings.xml index 7231729..1ed655f 100644 --- a/androidtagview/src/main/res/values/strings.xml +++ b/androidtagview/src/main/res/values/strings.xml @@ -1,3 +1,3 @@ - - AndroidTagView - + + AndroidTagView + diff --git a/androidtagview/src/test/java/co/lujun/androidtagview/ExampleUnitTest.java b/androidtagview/src/test/java/co/lujun/androidtagview/ExampleUnitTest.java index 5218bbd..b45bba8 100644 --- a/androidtagview/src/test/java/co/lujun/androidtagview/ExampleUnitTest.java +++ b/androidtagview/src/test/java/co/lujun/androidtagview/ExampleUnitTest.java @@ -1,15 +1,15 @@ -package co.lujun.androidtagview; - -import org.junit.Test; - -import static org.junit.Assert.*; - -/** - * To work on unit tests, switch the Test Artifact in the Build Variants view. - */ -public class ExampleUnitTest { - @Test - public void addition_isCorrect() throws Exception { - assertEquals(4, 2 + 2); - } +package co.lujun.androidtagview; + +import org.junit.Test; + +import static org.junit.Assert.*; + +/** + * To work on unit tests, switch the Test Artifact in the Build Variants view. + */ +public class ExampleUnitTest { + @Test + public void addition_isCorrect() throws Exception { + assertEquals(4, 2 + 2); + } } \ No newline at end of file diff --git a/build.gradle b/build.gradle index 51dd7a4..a115e91 100644 --- a/build.gradle +++ b/build.gradle @@ -1,25 +1,27 @@ -// Top-level build file where you can add configuration options common to all sub-projects/modules. - -buildscript { - repositories { - jcenter() - } - dependencies { - classpath 'com.android.tools.build:gradle:1.5.0' - classpath 'com.github.dcendents:android-maven-gradle-plugin:1.3' - classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.0' - - // NOTE: Do not place your application dependencies here; they belong - // in the individual module build.gradle files - } -} - -allprojects { - repositories { - jcenter() - } -} - -task clean(type: Delete) { - delete rootProject.buildDir -} +// Top-level build file where you can add configuration options common to all sub-projects/modules. + +buildscript { + repositories { + jcenter() + google() + } + dependencies { + classpath 'com.android.tools.build:gradle:3.2.1' + classpath 'com.github.dcendents:android-maven-gradle-plugin:1.4.1' + classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.4' + + // NOTE: Do not place your application dependencies here; they belong + // in the individual module build.gradle files + } +} + +allprojects { + repositories { + jcenter() + google() + } +} + +task clean(type: Delete) { + delete rootProject.buildDir +} diff --git a/gradle.properties b/gradle.properties index 1d3591c..a565221 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,18 +1,17 @@ -# Project-wide Gradle settings. - -# IDE (e.g. Android Studio) users: -# Gradle settings configured through the IDE *will override* -# any settings specified in this file. - +## Project-wide Gradle settings. +# # For more details on how to configure your build environment visit # http://www.gradle.org/docs/current/userguide/build_environment.html - +# # Specifies the JVM arguments used for the daemon process. # The setting is particularly useful for tweaking memory settings. # Default value: -Xmx10248m -XX:MaxPermSize=256m # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 - +# # When configured, Gradle will run in incubating parallel mode. # This option should only be used with decoupled projects. More details, visit # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects -# org.gradle.parallel=true \ No newline at end of file +# org.gradle.parallel=true +#Sun Jan 24 18:50:31 CST 2016 +android.useAndroidX=true +android.enableJetifier=true diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index f23df6e..1be1c62 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Wed Oct 21 11:34:03 PDT 2015 +#Thu Jun 14 09:04:10 ICT 2018 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-2.8-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-4.9-all.zip diff --git a/gradlew.bat b/gradlew.bat index 8a0b282..aec9973 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -1,90 +1,90 @@ -@if "%DEBUG%" == "" @echo off -@rem ########################################################################## -@rem -@rem Gradle startup script for Windows -@rem -@rem ########################################################################## - -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal - -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS= - -set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. -set APP_BASE_NAME=%~n0 -set APP_HOME=%DIRNAME% - -@rem Find java.exe -if defined JAVA_HOME goto findJavaFromJavaHome - -set JAVA_EXE=java.exe -%JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto init - -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:findJavaFromJavaHome -set JAVA_HOME=%JAVA_HOME:"=% -set JAVA_EXE=%JAVA_HOME%/bin/java.exe - -if exist "%JAVA_EXE%" goto init - -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:init -@rem Get command-line arguments, handling Windowz variants - -if not "%OS%" == "Windows_NT" goto win9xME_args -if "%@eval[2+2]" == "4" goto 4NT_args - -:win9xME_args -@rem Slurp the command line arguments. -set CMD_LINE_ARGS= -set _SKIP=2 - -:win9xME_args_slurp -if "x%~1" == "x" goto execute - -set CMD_LINE_ARGS=%* -goto execute - -:4NT_args -@rem Get arguments from the 4NT Shell from JP Software -set CMD_LINE_ARGS=%$ - -:execute -@rem Setup the command line - -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar - -@rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% - -:end -@rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd - -:fail -rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of -rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 - -:mainEnd -if "%OS%"=="Windows_NT" endlocal - -:omega +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windowz variants + +if not "%OS%" == "Windows_NT" goto win9xME_args +if "%@eval[2+2]" == "4" goto 4NT_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* +goto execute + +:4NT_args +@rem Get arguments from the 4NT Shell from JP Software +set CMD_LINE_ARGS=%$ + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/sample/.gitignore b/sample/.gitignore index fbe08b1..6c9b4c3 100755 --- a/sample/.gitignore +++ b/sample/.gitignore @@ -1,46 +1,46 @@ -.gradle -/local.properties -/.idea/workspace.xml -.DS_Store -/build -# built application files -*.ap_ - -# files for the dex VM -*.dex - -# Java class files -*.class -.DS_Store - -# generated files -bin/ -gen/ -Wiki/ - -# Local configuration file (sdk path, etc) -local.properties - -# Eclipse project files -.classpath -.project -.settings/ - -# Proguard folder generated by Eclipse -proguard/ - -#Android Studio -build/ -src/androidTest/ - -# Intellij project files -*.iml -*.ipr -*.iws -.idea/ - -#gradle -.gradle/ -.idea -app/build +.gradle +/local.properties +/.idea/workspace.xml +.DS_Store +/build +# built application files +*.ap_ + +# files for the dex VM +*.dex + +# Java class files +*.class +.DS_Store + +# generated files +bin/ +gen/ +Wiki/ + +# Local configuration file (sdk path, etc) +local.properties + +# Eclipse project files +.classpath +.project +.settings/ + +# Proguard folder generated by Eclipse +proguard/ + +#Android Studio +build/ +src/androidTest/ + +# Intellij project files +*.iml +*.ipr +*.iws +.idea/ + +#gradle +.gradle/ +.idea +app/build app/src/androidTest \ No newline at end of file diff --git a/sample/build.gradle b/sample/build.gradle index 2dcb20a..db7ac6a 100644 --- a/sample/build.gradle +++ b/sample/build.gradle @@ -1,28 +1,33 @@ -apply plugin: 'com.android.application' - -android { - compileSdkVersion 23 - buildToolsVersion "22.0.1" - - defaultConfig { - applicationId "co.lujun.sample" - minSdkVersion 9 - targetSdkVersion 23 - versionCode 1 - versionName "1.0" - } - buildTypes { - release { - minifyEnabled false - proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' - } - } -} - -dependencies { - compile fileTree(dir: 'libs', include: ['*.jar']) - compile project(':androidtagview') - testCompile 'junit:junit:4.12' - compile 'com.android.support:appcompat-v7:23.1.1' - compile 'com.android.support:design:23.1.1' -} +apply plugin: 'com.android.application' + +android { + compileSdkVersion 28 + + defaultConfig { + applicationId "co.lujun.sample" + minSdkVersion 14 + targetSdkVersion 28 + versionCode 1 + versionName "1.0" + } + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } +} + +repositories { + mavenCentral() + maven { url 'https://maven.google.com' } +} + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) + implementation project(':androidtagview') + testImplementation 'junit:junit:4.12' + implementation 'androidx.appcompat:appcompat:1.0.1' + implementation 'com.google.android.material:material:1.0.0' + implementation 'com.github.bumptech.glide:glide:4.1.1' +} diff --git a/sample/proguard-rules.pro b/sample/proguard-rules.pro index 700c8df..6828981 100644 --- a/sample/proguard-rules.pro +++ b/sample/proguard-rules.pro @@ -1,17 +1,17 @@ -# Add project specific ProGuard rules here. -# By default, the flags in this file are appended to flags specified -# in F:\code\android\adt-bundle-windows-x86_64-20140702\sdk/tools/proguard/proguard-android.txt -# You can edit the include path and order by changing the proguardFiles -# directive in build.gradle. -# -# For more details, see -# http://developer.android.com/guide/developing/tools/proguard.html - -# Add any project specific keep options here: - -# If your project uses WebView with JS, uncomment the following -# and specify the fully qualified class name to the JavaScript interface -# class: -#-keepclassmembers class fqcn.of.javascript.interface.for.webview { -# public *; -#} +# Add project specific ProGuard rules here. +# By default, the flags in this file are appended to flags specified +# in F:\code\android\adt-bundle-windows-x86_64-20140702\sdk/tools/proguard/proguard-android.txt +# You can edit the include path and order by changing the proguardFiles +# directive in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# Add any project specific keep options here: + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} diff --git a/sample/sample-release.apk b/sample/sample-release.apk index bb703b2..4676aae 100644 Binary files a/sample/sample-release.apk and b/sample/sample-release.apk differ diff --git a/sample/src/main/AndroidManifest.xml b/sample/src/main/AndroidManifest.xml index 9ef8ab9..e56e5f9 100644 --- a/sample/src/main/AndroidManifest.xml +++ b/sample/src/main/AndroidManifest.xml @@ -1,23 +1,23 @@ - - - - - - - - - - - - - - + + + + + + + + + + + + + + diff --git a/sample/src/main/assets/iran_sans.ttf b/sample/src/main/assets/iran_sans.ttf new file mode 100644 index 0000000..91885af Binary files /dev/null and b/sample/src/main/assets/iran_sans.ttf differ diff --git a/sample/src/main/java/co/lujun/sample/MainActivity.java b/sample/src/main/java/co/lujun/sample/MainActivity.java index 8388ae3..9399ad4 100644 --- a/sample/src/main/java/co/lujun/sample/MainActivity.java +++ b/sample/src/main/java/co/lujun/sample/MainActivity.java @@ -1,24 +1,37 @@ package co.lujun.sample; import android.app.AlertDialog; +import android.content.ClipData; +import android.content.Context; import android.content.DialogInterface; +import android.graphics.Bitmap; +import android.graphics.Color; import android.os.Bundle; -import android.support.v7.app.AppCompatActivity; -import android.support.v7.widget.Toolbar; +import android.view.LayoutInflater; import android.view.View; +import android.view.ViewGroup; import android.widget.Button; import android.widget.EditText; import android.widget.Toast; +import com.bumptech.glide.Glide; +import com.bumptech.glide.request.RequestOptions; +import com.bumptech.glide.request.target.SimpleTarget; +import com.bumptech.glide.request.transition.Transition; + import java.util.ArrayList; import java.util.List; +import androidx.appcompat.app.AppCompatActivity; +import androidx.appcompat.widget.Toolbar; +import androidx.recyclerview.widget.RecyclerView; import co.lujun.androidtagview.TagContainerLayout; import co.lujun.androidtagview.TagView; public class MainActivity extends AppCompatActivity { - private TagContainerLayout mTagContainerLayout1, mTagContainerLayout2, mTagContainerLayout3; + private TagContainerLayout mTagContainerLayout1, mTagContainerLayout2, + mTagContainerLayout3, mTagContainerLayout4, mTagcontainerLayout5; @Override protected void onCreate(Bundle savedInstanceState) { @@ -29,15 +42,13 @@ protected void onCreate(Bundle savedInstanceState) { List list1 = new ArrayList(); list1.add("Java"); - list1.add("C/C++"); + list1.add("C++"); list1.add("Python"); list1.add("Swift"); list1.add("你好,这是一个TAG。你好,这是一个TAG。你好,这是一个TAG。你好,这是一个TAG。"); list1.add("PHP"); - list1.add("Python"); list1.add("JavaScript"); list1.add("Html"); - list1.add("Hello, this is a TAG example."); list1.add("Welcome to use AndroidTagView!"); List list2 = new ArrayList(); @@ -50,17 +61,26 @@ protected void onCreate(Bundle savedInstanceState) { list2.add("UK"); list2.add("Germany"); list2.add("Niger"); - list2.add("Singapore"); list2.add("Poland"); list2.add("Norway"); list2.add("Uruguay"); list2.add("Brazil"); - String[] list3 = new String[]{"Adele", "Avril Lavigne", "Taylor Swift", "Rihanna", " Whitney Houston"}; + String[] list3 = new String[]{"Persian", "波斯语", "فارسی", "Hello", "你好", "سلام"}; + String[] list4 = new String[]{"Adele", "Whitney Houston"}; + + List list5 = new ArrayList(); + list5.add("Custom Red Color"); + list5.add("Custom Blue Color"); + mTagContainerLayout1 = (TagContainerLayout) findViewById(R.id.tagcontainerLayout1); mTagContainerLayout2 = (TagContainerLayout) findViewById(R.id.tagcontainerLayout2); mTagContainerLayout3 = (TagContainerLayout) findViewById(R.id.tagcontainerLayout3); + mTagContainerLayout4 = (TagContainerLayout) findViewById(R.id.tagcontainerLayout4); + mTagcontainerLayout5 = (TagContainerLayout) findViewById(R.id.tagcontainerLayout5); + + mTagContainerLayout1.setDefaultImageDrawableID(R.drawable.yellow_avatar); // Set custom click listener mTagContainerLayout1.setOnTagClickListener(new TagView.OnTagClickListener() { @@ -78,7 +98,9 @@ public void onTagLongClick(final int position, String text) { .setPositiveButton("Delete", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { - mTagContainerLayout1.removeTag(position); + if (position < mTagContainerLayout1.getChildCount()) { + mTagContainerLayout1.removeTag(position); + } } }) .setNegativeButton("Cancel", new DialogInterface.OnClickListener() { @@ -90,6 +112,55 @@ public void onClick(DialogInterface dialog, int which) { .create(); dialog.show(); } + + @Override + public void onSelectedTagDrag(int position, String text) {} + + @Override + public void onTagCrossClick(int position) { +// mTagContainerLayout1.removeTag(position); + Toast.makeText(MainActivity.this, "Click TagView cross! position = " + position, + Toast.LENGTH_SHORT).show(); + } + }); + + mTagContainerLayout3.setOnTagClickListener(new TagView.OnTagClickListener() { + @Override + public void onTagClick(int position, String text) { + List selectedPositions = mTagContainerLayout3.getSelectedTagViewPositions(); + //deselect all tags when click on an unselected tag. Otherwise show toast. + if (selectedPositions.isEmpty() || selectedPositions.contains(position)) { + Toast.makeText(MainActivity.this, "click-position:" + position + ", text:" + text, + Toast.LENGTH_SHORT).show(); + } else { + //deselect all tags + for (int i : selectedPositions) { + mTagContainerLayout3.deselectTagView(i); + } + } + + } + + @Override + public void onTagLongClick(final int position, String text) { + mTagContainerLayout3.toggleSelectTagView(position); + + List selectedPositions = mTagContainerLayout3.getSelectedTagViewPositions(); + Toast.makeText(MainActivity.this, "selected-positions:" + selectedPositions.toString(), + Toast.LENGTH_SHORT).show(); + } + + @Override + public void onSelectedTagDrag(int position, String text) { + ClipData clip = ClipData.newPlainText("Text", text); + View view = mTagContainerLayout3.getTagView(position); + View.DragShadowBuilder shadow = new View.DragShadowBuilder(view); + view.startDrag(clip, shadow, Boolean.TRUE, 0); + } + + @Override + public void onTagCrossClick(int position) { + } }); // Custom settings @@ -101,12 +172,31 @@ public void onClick(DialogInterface dialog, int which) { // If you want to use your colors for TagView, remember set the theme with ColorFactory.NONE // mTagContainerLayout1.setTheme(ColorFactory.NONE); // mTagContainerLayout1.setTagBackgroundColor(Color.TRANSPARENT); +// mTagContainerLayout1.setTagTextDirection(View.TEXT_DIRECTION_RTL); + + // support typeface +// Typeface typeface = Typeface.createFromAsset(getAssets(), "iran_sans.ttf"); +// mTagContainerLayout.setTagTypeface(typeface); + + // adjust distance baseline and descent +// mTagContainerLayout.setTagBdDistance(4.6f); // After you set your own attributes for TagView, then set tag(s) or add tag(s) mTagContainerLayout1.setTags(list1); + loadImages(list1); mTagContainerLayout2.setTags(list2); mTagContainerLayout3.setTags(list3); + mTagContainerLayout4.setTags(list4); + + List colors = new ArrayList(); + //int[]color = {backgroundColor, tagBorderColor, tagTextColor, tagSelectedBackgroundColor} + int[] col1 = {Color.parseColor("#ff0000"), Color.parseColor("#000000"), Color.parseColor("#ffffff"), Color.parseColor("#999999")}; + int[] col2 = {Color.parseColor("#0000ff"), Color.parseColor("#000000"), Color.parseColor("#ffffff"), Color.parseColor("#999999")}; + colors.add(col1); + colors.add(col2); + + mTagcontainerLayout5.setTags(list5, colors); final EditText text = (EditText) findViewById(R.id.text_tag); Button btnAddTag = (Button) findViewById(R.id.btn_add_tag); btnAddTag.setOnClickListener(new View.OnClickListener() { @@ -117,5 +207,101 @@ public void onClick(View v) { // mTagContainerLayout1.addTag(text.getText().toString(), 4); } }); + +// mTagContainerLayout1.setMaxLines(1); + + + // test in RecyclerView +// RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recyclerView); +// recyclerView.setVisibility(View.VISIBLE); +// TagRecyclerViewAdapter adapter = new TagRecyclerViewAdapter(this, list3); +// adapter.setOnClickListener(new View.OnClickListener() { +// @Override +// public void onClick(View v) { +// Toast.makeText(MainActivity.this, "Click on TagContainerLayout", Toast.LENGTH_SHORT).show(); +// } +// }); +// recyclerView.setAdapter(adapter); + } + + private void loadImages(List list) { + String[] avatars = new String[]{"https://forums.oneplus.com/data/avatars/m/231/231279.jpg", + "https://d1marr3m5x4iac.cloudfront.net/images/block/movies/17214/17214_aa.jpg", + "https://lh3.googleusercontent.com/-KSI1bJ1aVS4/AAAAAAAAAAI/AAAAAAAAB9c/Vrgt6WyS5OU/il/photo.jpg"}; + + for (int i=0; i() { + @Override + public void onResourceReady(Bitmap resource, Transition transition) { + mTagContainerLayout1.getTagView(index).setImage(resource); + } + }); + try { + Thread.sleep(50); + } catch (InterruptedException e) { + System.err.println(e.getMessage()); + } + } + } + + public class TagRecyclerViewAdapter + extends RecyclerView.Adapter { + + private Context mContext; + private String[] mData; + private View.OnClickListener mOnClickListener; + + public TagRecyclerViewAdapter(Context context, String[] data) { + this.mContext = context; + this.mData = data; + } + + @Override + public int getItemCount() { + return 10; + } + + @Override + public TagViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + return new TagViewHolder(LayoutInflater.from(mContext) + .inflate(R.layout.view_recyclerview_item, parent, false), mOnClickListener); + } + + @Override + public void onBindViewHolder(TagViewHolder holder, int position) { + holder.tagContainerLayout.setTags(mData); + holder.button.setOnClickListener(mOnClickListener); + } + + public void setOnClickListener(View.OnClickListener listener) { + this.mOnClickListener = listener; + } + + class TagViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener { + + TagContainerLayout tagContainerLayout; + View.OnClickListener clickListener; + Button button; + + public TagViewHolder(View v, View.OnClickListener listener) { + super(v); + this.clickListener = listener; + tagContainerLayout = (TagContainerLayout) v.findViewById(R.id.tagcontainerLayout); + button = (Button) v.findViewById(R.id.button); +// v.setOnClickListener(this); + } + + @Override + public void onClick(View v) { + if (clickListener != null) { + clickListener.onClick(v); + } + } + } } } diff --git a/sample/src/main/res/drawable/yellow_avatar.png b/sample/src/main/res/drawable/yellow_avatar.png new file mode 100644 index 0000000..605a8e7 Binary files /dev/null and b/sample/src/main/res/drawable/yellow_avatar.png differ diff --git a/sample/src/main/res/layout/activity_main.xml b/sample/src/main/res/layout/activity_main.xml index 3f0ab6f..12311a8 100644 --- a/sample/src/main/res/layout/activity_main.xml +++ b/sample/src/main/res/layout/activity_main.xml @@ -1,26 +1,26 @@ - - - - - - - - - - - - + + + + + + + + + + + + diff --git a/sample/src/main/res/layout/content_main.xml b/sample/src/main/res/layout/content_main.xml index bfd610d..bb89e68 100644 --- a/sample/src/main/res/layout/content_main.xml +++ b/sample/src/main/res/layout/content_main.xml @@ -1,86 +1,138 @@ - - - - - - - - - -