diff --git a/.travis.yml b/.travis.yml index 64dae33..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 +jdk: oraclejdk8 - # The SDK version used to compile your project - - android-23 +env: + global: + - ANDROID_ABI=armeabi-v7a - # Additional components - - extra-google-google_play_services - - extra-google-m2repository +android: + components: + - tools + - android-22 + - android-28 + - sys-img-armeabi-v7a-android-22 - extra-android-m2repository - - addon-google_apis-google-19 + - extra-android-support + - extra + licenses: + - android-sdk-license-.+ + +before_install: + - mkdir "$ANDROID_HOME/licenses" || true + - echo "d56f5187479451eabf01fb78af6dfcb131a6481e" > "$ANDROID_HOME/licenses/android-sdk-license" - # 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 +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 assembleDebug \ No newline at end of file +script: + - ./gradlew clean connectedAndroidTest \ No newline at end of file diff --git a/README.md b/README.md index 9c3f7dd..6acc61b 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ An Android TagView library. You can customize awesome TagView by using this libr ## Screenshots -androidtagview_record_1.gif device-2016-01-16-233617.png +androidtagview_record_1.gif device-2016-11-09-223523.png ## Usage @@ -16,7 +16,8 @@ Add below dependency in your **build.gradle** file. ```groovy dependencies { - compile 'co.lujun:androidtagview:1.0.3' + implementation 'co.lujun:androidtagview:1.1.7' + implementation 'androidx.appcompat:appcompat:1.0.1' } ``` @@ -61,19 +62,31 @@ Now, you have successfully created some TagViews. The following will show some m | 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 20px) -| tag_vertical_padding | dimension | Vertical padding for TagView, include top and bottom padding(top and bottom padding are equal, default 17px) +| 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 5.5px) +| 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 unclickable) +| 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.** @@ -81,7 +94,7 @@ Now, you have successfully created some TagViews. The following will show some m |theme|code|value|description |:---:|:---:|:---:|:---:| -| none | ColorFactory.NONE | -1 | If you customize TagView with your way, set this theme +| 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 @@ -103,7 +116,7 @@ Now, you have successfully created some TagViews. The following will show some m ## Methods -* Set a ```TagView.OnTagClickListener``` for TagView, for ```onTagClick``` and ```onTagLongClick``` callback +* Set a ```TagView.OnTagClickListener``` for TagView, for ```onTagClick``` , ```onTagLongClick``` and ```onTagCrossClick``` callback ```java mTagContainerLayout.setOnTagClickListener(new TagView.OnTagClickListener() { @@ -116,6 +129,16 @@ mTagContainerLayout.setOnTagClickListener(new TagView.OnTagClickListener() { 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. @@ -168,7 +191,7 @@ mTagContainerLayout.addTag(String text); ```java mTagContainerLayout.addTag(String text, int position); ``` -* Remove TagView on particular position, require the position of the TagView +* Remove TagView on particular position, require the position of the TagView. ```java mTagContainerLayout.removeTag(int position); ``` @@ -176,23 +199,74 @@ mTagContainerLayout.removeTag(int position); ```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.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.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.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) +### 1.0.0(2016-1-6) - First release ## Sample App @@ -215,4 +289,4 @@ If you have any questions, contact me: [lujun.byte#gmail.com](mailto:lujun.byte@ 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 + limitations under the License. diff --git a/androidtagview/build.gradle b/androidtagview/build.gradle index 8839da5..a04e40a 100644 --- a/androidtagview/build.gradle +++ b/androidtagview/build.gradle @@ -1,14 +1,13 @@ apply plugin: 'com.android.library' android { - compileSdkVersion 23 - buildToolsVersion "22.0.1" + compileSdkVersion 28 defaultConfig { - minSdkVersion 9 - targetSdkVersion 23 - versionCode 103 - versionName "1.0.3" + minSdkVersion 14 + targetSdkVersion 28 + versionCode 117 + versionName "1.1.7" } buildTypes { release { @@ -19,8 +18,8 @@ android { } dependencies { - compile fileTree(dir: 'libs', include: ['*.jar']) - testCompile 'junit:junit:4.12' - compile 'com.android.support:appcompat-v7:23.1.1' + 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/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/java/co/lujun/androidtagview/ColorFactory.java b/androidtagview/src/main/java/co/lujun/androidtagview/ColorFactory.java index e51a7ce..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; @@ -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 e738735..e36dccf 100644 --- a/androidtagview/src/main/java/co/lujun/androidtagview/TagContainerLayout.java +++ b/androidtagview/src/main/java/co/lujun/androidtagview/TagContainerLayout.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.content.Context; @@ -7,7 +23,6 @@ import android.graphics.Paint; import android.graphics.RectF; import android.graphics.Typeface; -import android.support.v4.widget.ViewDragHelper; import android.util.AttributeSet; import android.view.Gravity; import android.view.MotionEvent; @@ -16,92 +31,181 @@ 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)*/ + /** + * Vertical interval, default 5(dp) + */ private int mVerticalInterval; - /** Horizontal interval, default 5(dp)*/ + /** + * 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)*/ + /** + * TagContainerLayout border width(default 0.5dp) + */ private float mBorderWidth = 0.5f; - /** TagContainerLayout border radius(default 10.0dp)*/ + /** + * TagContainerLayout border radius(default 10.0dp) + */ private float mBorderRadius = 10.0f; - /** The sensitive of the ViewDragHelper(default 1.0f, normal)*/ + /** + * The sensitive of the ViewDragHelper(default 1.0f, normal) + */ private float mSensitivity = 1.0f; - /** TagView average height*/ + /** + * TagView average height + */ private int mChildHeight; - /** TagContainerLayout border color(default #22FF0000)*/ + /** + * TagContainerLayout border color(default #22FF0000) + */ private int mBorderColor = Color.parseColor("#22FF0000"); - /** TagContainerLayout background color(default #11FF0000)*/ + /** + * TagContainerLayout background color(default #11FF0000) + */ private int mBackgroundColor = Color.parseColor("#11FF0000"); - /** The container layout gravity(default left)*/ + /** + * The container layout gravity(default left) + */ private int mGravity = Gravity.LEFT; - /** The max length for TagView(default max length 23)*/ + /** + * 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)*/ + /** + * TagView Border width(default 0.5dp) + */ private float mTagBorderWidth = 0.5f; - /** TagView Border radius(default 15.0dp)*/ + /** + * TagView Border radius(default 15.0dp) + */ private float mTagBorderRadius = 15.0f; - /** TagView Text size(default 14sp)*/ + /** + * TagView Text size(default 14sp) + */ private float mTagTextSize = 14; - /** Text direction(support:TEXT_DIRECTION_RTL & TEXT_DIRECTION_LTR, default TEXT_DIRECTION_LTR)*/ + /** + * 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 20px)*/ - private int mTagHorizontalPadding = 20; + /** + * 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 17px)*/ - private int mTagVerticalPadding = 17; + /** + * Vertical padding for TagView, include top & bottom padding(top & bottom padding are equal, default 8dp) + */ + private int mTagVerticalPadding = 8; - /** TagView border color(default #88F44336)*/ + /** + * TagView border color(default #88F44336) + */ private int mTagBorderColor = Color.parseColor("#88F44336"); - /** TagView background color(default #33F44336)*/ + /** + * TagView background color(default #33F44336) + */ private int mTagBackgroundColor = Color.parseColor("#33F44336"); - /** TagView text color(default #FF666666)*/ + /** + * 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*/ + /** + * TagView typeface + */ private Typeface mTagTypeface = Typeface.DEFAULT; - /** Whether TagView can clickable(default unclickable)*/ + /** + * Whether TagView can clickable(default unclickable) + */ private boolean isTagViewClickable; - /** Tags*/ + /** + * Whether TagView can selectable(default unselectable) + */ + private boolean isTagViewSelectable; + + /** + * Tags + */ private List mTags; - /** Can drag TagView(default false)*/ + /** + * Default image for new tags + */ + private int mDefaultImageDrawableID = -1; + + /** + * Can drag TagView(default false) + */ private boolean mDragEnable; - /** TagView drag state(default STATE_IDLE)*/ + /** + * TagView drag state(default STATE_IDLE) + */ private int mTagViewState = ViewDragHelper.STATE_IDLE; - /** The distance between baseline and descent(default 5.5px)*/ - private float mTagBdDistance = 5.5f; + /** + * The distance between baseline and descent(default 2.75dp) + */ + private float mTagBdDistance = 2.75f; - /** OnTagClickListener for TagView*/ + /** + * 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; @@ -112,15 +216,66 @@ public class TagContainerLayout extends ViewGroup { private int[] mViewPos; - /** View theme(default PURE_CYAN)*/ + /** + * View theme(default PURE_CYAN) + */ private int mTheme = ColorFactory.PURE_CYAN; - /** Default interval(dp)*/ + /** + * Default interval(dp) + */ private static final float DEFAULT_INTERVAL = 5; - /** Default tag min length*/ + /** + * 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); } @@ -129,24 +284,24 @@ public TagContainerLayout(Context context, AttributeSet attrs) { this(context, attrs, 0); } - public TagContainerLayout(Context context, AttributeSet attrs, int defStyleAttr){ + 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){ + 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, + mVerticalInterval = (int) attributes.getDimension(R.styleable.AndroidTagView_vertical_interval, dp2px(context, DEFAULT_INTERVAL)); - mHorizontalInterval = (int)attributes.getDimension(R.styleable.AndroidTagView_horizontal_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, - mTagBdDistance); + dp2px(context, mTagBdDistance)); mBorderColor = attributes.getColor(R.styleable.AndroidTagView_container_border_color, mBorderColor); mBackgroundColor = attributes.getColor(R.styleable.AndroidTagView_container_background_color, @@ -155,6 +310,7 @@ private void init(Context context, AttributeSet attrs, int defStyleAttr){ 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, @@ -162,9 +318,10 @@ private void init(Context context, AttributeSet attrs, int defStyleAttr){ mTagBorderRadius = attributes.getDimension( R.styleable.AndroidTagView_tag_corner_radius, dp2px(context, mTagBorderRadius)); mTagHorizontalPadding = (int) attributes.getDimension( - R.styleable.AndroidTagView_tag_horizontal_padding, mTagHorizontalPadding); + R.styleable.AndroidTagView_tag_horizontal_padding, + dp2px(context, mTagHorizontalPadding)); mTagVerticalPadding = (int) attributes.getDimension( - R.styleable.AndroidTagView_tag_vertical_padding, mTagVerticalPadding); + 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, @@ -174,6 +331,22 @@ private void init(Context context, AttributeSet attrs, int defStyleAttr){ 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); @@ -184,6 +357,10 @@ private void init(Context context, AttributeSet attrs, int defStyleAttr){ setTagMaxLength(mTagMaxLength); setTagHorizontalPadding(mTagHorizontalPadding); setTagVerticalPadding(mTagVerticalPadding); + + if (isInEditMode()) { + addTag("sample tag"); + } } @Override @@ -198,13 +375,13 @@ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec); int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec); - if (childCount == 0){ + if (childCount == 0) { setMeasuredDimension(0, 0); - }else if (heightSpecMode == MeasureSpec.AT_MOST + } else if (heightSpecMode == MeasureSpec.AT_MOST || heightSpecMode == MeasureSpec.UNSPECIFIED) { setMeasuredDimension(widthSpecSize, (mVerticalInterval + mChildHeight) * lines - mVerticalInterval + getPaddingTop() + getPaddingBottom()); - }else { + } else { setMeasuredDimension(widthSpecSize, heightSpecSize); } } @@ -218,7 +395,7 @@ protected void onSizeChanged(int w, int h, int oldw, int oldh) { @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { int childCount; - if ((childCount = getChildCount()) <= 0){ + if ((childCount = getChildCount()) <= 0) { return; } int availableW = getMeasuredWidth() - getPaddingLeft() - getPaddingRight(); @@ -232,16 +409,16 @@ protected void onLayout(boolean changed, int l, int t, int r, int b) { final View childView = getChildAt(i); if (childView.getVisibility() != GONE) { int width = childView.getMeasuredWidth(); - if (mGravity == Gravity.RIGHT){ - if (curRight - width < getPaddingLeft()){ + 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){ + } 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++) { @@ -255,15 +432,15 @@ protected void onLayout(boolean changed, int l, int t, int r, int b) { mViewPos[i * 2 + 1] = curTop; curLeft += width + mHorizontalInterval; - if (i == childCount - 1){ + 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){ + } else { + if (curLeft + width - getPaddingLeft() > availableW) { curLeft = getPaddingLeft(); curTop += mChildHeight + mVerticalInterval; } @@ -311,12 +488,12 @@ public boolean onTouchEvent(MotionEvent event) { @Override public void computeScroll() { super.computeScroll(); - if (mViewDragHelper.continueSettling(true)){ + if (mViewDragHelper.continueSettling(true)) { requestLayout(); } } - private int getChildLines(int childCount){ + private int getChildLines(int childCount) { int availableW = getMeasuredWidth() - getPaddingLeft() - getPaddingRight(); int lines = 1; for (int i = 0, curLineW = 0; i < childCount; i++) { @@ -325,34 +502,35 @@ private int getChildLines(int childCount){ int height = childView.getMeasuredHeight(); mChildHeight = i == 0 ? height : Math.min(mChildHeight, height); curLineW += dis; - if (curLineW - mHorizontalInterval > availableW){ + if (curLineW - mHorizontalInterval > availableW) { lines++; curLineW = dis; } } - return lines; + + return mMaxLines <= 0 ? lines : mMaxLines; } - private int[] onUpdateColorFactory(){ + private int[] onUpdateColorFactory() { int[] colors; - if (mTheme == ColorFactory.RANDOM){ + if (mTheme == ColorFactory.RANDOM) { colors = ColorFactory.onRandomBuild(); - }else if (mTheme == ColorFactory.PURE_TEAL){ + } else if (mTheme == ColorFactory.PURE_TEAL) { colors = ColorFactory.onPureBuild(ColorFactory.PURE_COLOR.TEAL); - }else if (mTheme == ColorFactory.PURE_CYAN){ + } else if (mTheme == ColorFactory.PURE_CYAN) { colors = ColorFactory.onPureBuild(ColorFactory.PURE_COLOR.CYAN); - }else { - colors = new int[]{mTagBackgroundColor, mTagBorderColor, mTagTextColor}; + } else { + colors = new int[]{mTagBackgroundColor, mTagBorderColor, mTagTextColor, mSelectedTagBackgroundColor}; } return colors; } - private void onSetTag(){ - if (mTags == null){ + private void onSetTag() { + if (mTags == null) { throw new RuntimeException("NullPointer exception!"); } removeAllTags(); - if (mTags.size() == 0){ + if (mTags.size() == 0) { return; } for (int i = 0; i < mTags.size(); i++) { @@ -362,27 +540,44 @@ private void onSetTag(){ } private void onAddTag(String text, int position) { - if (position < 0 || position > mChildViews.size()){ + if (position < 0 || position > mChildViews.size()) { throw new RuntimeException("Illegal position!"); } - TagView tagView = new TagView(getContext(), text); - initTagView(tagView); + 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()){ + if (position < mChildViews.size()) { for (int i = position; i < mChildViews.size(); i++) { mChildViews.get(i).setTag(i); } - }else { + } else { tagView.setTag(position); } addView(tagView, position); } - private void initTagView(TagView tagView){ - int[] colors = onUpdateColorFactory(); + 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); @@ -392,19 +587,30 @@ private void initTagView(TagView tagView){ tagView.setHorizontalPadding(mTagHorizontalPadding); tagView.setVerticalPadding(mTagVerticalPadding); tagView.setIsViewClickable(isTagViewClickable); + tagView.setIsViewSelectable(isTagViewSelectable); tagView.setBdDistance(mTagBdDistance); tagView.setOnTagClickListener(mOnTagClickListener); - } - - private void invalidateTags(){ + 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()){ + private void onRemoveTag(int position) { + if (position < 0 || position >= mChildViews.size()) { throw new RuntimeException("Illegal position!"); } mChildViews.remove(position); @@ -415,27 +621,42 @@ private void onRemoveTag(int position){ // TODO, make removed view null? } - private int[] onGetNewPosition(View view){ + 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 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]); + 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){ + 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){ + } else { + if (Math.abs(left - mViewPos[i * 2]) < tmpLeftDis) { bestMatchLeft = mViewPos[i * 2]; tmpLeftDis = Math.abs(left - bestMatchLeft); } @@ -446,17 +667,17 @@ private int[] onGetNewPosition(View view){ return new int[]{bestMatchLeft, bestMatchTop}; } - private int onGetCoordinateReferPos(int left, int top){ - int pos = 0; + 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]){ + if (left == mViewPos[i * 2] && top == mViewPos[i * 2 + 1]) { pos = i; } } return pos; } - private void onChangeView(View view, int newPos, int originPos){ + private void onChangeView(View view, int newPos, int originPos) { mChildViews.remove(originPos); mChildViews.add(newPos, view); for (View child : mChildViews) { @@ -467,11 +688,11 @@ private void onChangeView(View view, int newPos, int originPos){ addView(view, newPos); } - private int ceilTagBorderWidth(){ - return (int)Math.ceil(mTagBorderWidth); + private int ceilTagBorderWidth() { + return (int) Math.ceil(mTagBorderWidth); } - private class DragHelperCallBack extends ViewDragHelper.Callback{ + private class DragHelperCallBack extends ViewDragHelper.Callback { @Override public void onViewDragStateChanged(int state) { @@ -523,14 +744,16 @@ public void onViewReleased(View releasedChild, float xvel, float yvel) { /** * Get current drag view state. + * * @return */ - public int getTagViewState(){ + public int getTagViewState() { return mTagViewState; } /** * Get TagView text baseline and descent distance. + * * @return */ public float getTagBdDistance() { @@ -539,6 +762,7 @@ public float getTagBdDistance() { /** * Set TagView text baseline and descent distance. + * * @param tagBdDistance */ public void setTagBdDistance(float tagBdDistance) { @@ -547,54 +771,81 @@ public void setTagBdDistance(float 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){ + public void setTags(List tags, List colorArrayList) { mTags = tags; + mColorArrayList = colorArrayList; onSetTag(); } /** * Set tags + * * @param tags */ - public void setTags(String... 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){ + 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){ + public void addTag(String text, int position) { onAddTag(text, position); postInvalidate(); } /** * Remove a TagView in specified position. + * * @param position */ - public void removeTag(int 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(){ + public void removeAllTags() { mChildViews.clear(); removeAllViews(); postInvalidate(); @@ -602,30 +853,109 @@ public void removeAllTags(){ /** * Set OnTagClickListener for TagView. + * * @param listener */ - public void setOnTagClickListener(TagView.OnTagClickListener 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(); + public String getTagText(int position) { + return ((TagView) mChildViews.get(position)).getText(); } /** * Get a string list for all tags in TagContainerLayout. + * * @return */ - public List getTags(){ + public List getTags() { List tmpList = new ArrayList(); - for (View view : mChildViews){ - if (view instanceof TagView){ + for (View view : mChildViews) { + if (view instanceof TagView) { tmpList.add(((TagView) view).getText()); } } @@ -634,56 +964,63 @@ public List getTags(){ /** * Set whether the child view can be dragged. + * * @param enable */ - public void setDragEnable(boolean enable){ + public void setDragEnable(boolean enable) { this.mDragEnable = enable; } /** * Get current view is drag enable attribute. + * * @return */ - public boolean getDragEnable(){ + public boolean getDragEnable() { return mDragEnable; } /** * Set vertical interval + * * @param interval */ - public void setVerticalInterval(float interval){ + public void setVerticalInterval(float interval) { mVerticalInterval = (int) dp2px(getContext(), interval); postInvalidate(); } /** * Get vertical interval in this view. + * * @return */ - public int getVerticalInterval(){ + public int getVerticalInterval() { return mVerticalInterval; } /** * Set horizontal interval. + * * @param interval */ - public void setHorizontalInterval(float interval){ - mHorizontalInterval = (int)dp2px(getContext(), interval); + public void setHorizontalInterval(float interval) { + mHorizontalInterval = (int) dp2px(getContext(), interval); postInvalidate(); } /** * Get horizontal interval in this view. + * * @return */ - public int getHorizontalInterval(){ + public int getHorizontalInterval() { return mHorizontalInterval; } /** * Get TagContainerLayout border width. + * * @return */ public float getBorderWidth() { @@ -692,6 +1029,7 @@ public float getBorderWidth() { /** * Set TagContainerLayout border width. + * * @param width */ public void setBorderWidth(float width) { @@ -700,6 +1038,7 @@ public void setBorderWidth(float width) { /** * Get TagContainerLayout border radius. + * * @return */ public float getBorderRadius() { @@ -708,6 +1047,7 @@ public float getBorderRadius() { /** * Set TagContainerLayout border radius. + * * @param radius */ public void setBorderRadius(float radius) { @@ -716,6 +1056,7 @@ public void setBorderRadius(float radius) { /** * Get TagContainerLayout border color. + * * @return */ public int getBorderColor() { @@ -724,6 +1065,7 @@ public int getBorderColor() { /** * Set TagContainerLayout border color. + * * @param color */ public void setBorderColor(int color) { @@ -732,6 +1074,7 @@ public void setBorderColor(int color) { /** * Get TagContainerLayout background color. + * * @return */ public int getBackgroundColor() { @@ -740,6 +1083,7 @@ public int getBackgroundColor() { /** * Set TagContainerLayout background color. + * * @param color */ public void setBackgroundColor(int color) { @@ -748,6 +1092,7 @@ public void setBackgroundColor(int color) { /** * Get container layout gravity. + * * @return */ public int getGravity() { @@ -756,6 +1101,7 @@ public int getGravity() { /** * Set container layout gravity. + * * @param gravity */ public void setGravity(int gravity) { @@ -764,6 +1110,7 @@ public void setGravity(int gravity) { /** * Get TagContainerLayout ViewDragHelper sensitivity. + * * @return */ public float getSensitivity() { @@ -772,46 +1119,89 @@ public float getSensitivity() { /** * 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){ + public void setTagMaxLength(int maxLength) { mTagMaxLength = maxLength < TAG_MIN_LENGTH ? TAG_MIN_LENGTH : maxLength; } /** * Get TagView max length. + * * @return */ - public int getTagMaxLength(){ + public int getTagMaxLength() { return mTagMaxLength; } /** * Set TagView theme. + * * @param theme */ - public void setTheme(int theme){ + public void setTheme(int theme) { mTheme = theme; } /** * Get TagView theme. + * * @return */ - public int getTheme(){ + public int getTheme() { return mTheme; } /** * Get TagView is clickable. + * * @return */ public boolean getIsTagViewClickable() { @@ -820,14 +1210,34 @@ public boolean getIsTagViewClickable() { /** * 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() { @@ -836,6 +1246,7 @@ public float getTagBorderWidth() { /** * Set TagView border width. + * * @param width */ public void setTagBorderWidth(float width) { @@ -844,6 +1255,7 @@ public void setTagBorderWidth(float width) { /** * Get TagView border radius. + * * @return */ public float getTagBorderRadius() { @@ -852,6 +1264,7 @@ public float getTagBorderRadius() { /** * Set TagView border radius. + * * @param radius */ public void setTagBorderRadius(float radius) { @@ -860,6 +1273,7 @@ public void setTagBorderRadius(float radius) { /** * Get TagView text size. + * * @return */ public float getTagTextSize() { @@ -868,6 +1282,7 @@ public float getTagTextSize() { /** * Set TagView text size. + * * @param size */ public void setTagTextSize(float size) { @@ -876,6 +1291,7 @@ public void setTagTextSize(float size) { /** * Get TagView horizontal padding. + * * @return */ public int getTagHorizontalPadding() { @@ -884,6 +1300,7 @@ public int getTagHorizontalPadding() { /** * Set TagView horizontal padding. + * * @param padding */ public void setTagHorizontalPadding(int padding) { @@ -893,6 +1310,7 @@ public void setTagHorizontalPadding(int padding) { /** * Get TagView vertical padding. + * * @return */ public int getTagVerticalPadding() { @@ -901,6 +1319,7 @@ public int getTagVerticalPadding() { /** * Set TagView vertical padding. + * * @param padding */ public void setTagVerticalPadding(int padding) { @@ -910,6 +1329,7 @@ public void setTagVerticalPadding(int padding) { /** * Get TagView border color. + * * @return */ public int getTagBorderColor() { @@ -918,6 +1338,7 @@ public int getTagBorderColor() { /** * Set TagView border color. + * * @param color */ public void setTagBorderColor(int color) { @@ -926,6 +1347,7 @@ public void setTagBorderColor(int color) { /** * Get TagView background color. + * * @return */ public int getTagBackgroundColor() { @@ -934,6 +1356,7 @@ public int getTagBackgroundColor() { /** * Set TagView background color. + * * @param color */ public void setTagBackgroundColor(int color) { @@ -942,6 +1365,7 @@ public void setTagBackgroundColor(int color) { /** * Get TagView text color. + * * @return */ public int getTagTextColor() { @@ -951,6 +1375,7 @@ public int getTagTextColor() { /** * 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) { @@ -959,6 +1384,7 @@ public void setTagTextDirection(int textDirection) { /** * Get TagView typeface. + * * @return */ public Typeface getTagTypeface() { @@ -967,6 +1393,7 @@ public Typeface getTagTypeface() { /** * Set TagView typeface. + * * @param typeface */ public void setTagTypeface(Typeface typeface) { @@ -975,6 +1402,7 @@ public void setTagTypeface(Typeface typeface) { /** * Get tag text direction + * * @return */ public int getTagTextDirection() { @@ -983,19 +1411,201 @@ public int getTagTextDirection() { /** * 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; + /** + * 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; } - public float sp2px(Context context, float sp) { - final float scale = context.getResources().getDisplayMetrics().scaledDensity; - return sp * scale; + /** + * 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 2009be1..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.Path; import android.graphics.RectF; +import android.graphics.Region; +import android.graphics.Shader; import android.graphics.Typeface; -import android.support.v4.widget.ViewDragHelper; +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,22 +65,31 @@ 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*/ + /** Scroll slop threshold 4dp*/ private int mSlopThreshold = 4; /** How long trigger long click callback(default 500ms)*/ @@ -64,7 +101,10 @@ public class TagView extends View { /** The distance between baseline and descent*/ private float bdDistance; - private Paint mPaint; + /** Whether to support 'letters show with RTL(eg: Android to diordnA)' style(default false)*/ + private boolean mTagSupportLettersRTL = false; + + private Paint mPaint, mRipplePaint; private RectF mRectF; @@ -76,8 +116,37 @@ public class TagView extends View { 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() { @@ -93,13 +162,24 @@ public void run() { public TagView(Context context, String text){ super(context); - init(text); + init(context, text); } - private void init(String text){ + public TagView(Context context, String text, int defaultImageID){ + super(context); + init(context, text); + mBitmapImage = BitmapFactory.decodeResource(getResources(), defaultImageID); + } + + 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(); + mPath = new Path(); mOriginText = text == null ? "" : text; + mMoveSlop = (int) dp2px(context, mMoveSlop); + mSlopThreshold = (int) dp2px(context, mSlopThreshold); } private void onDealText(){ @@ -127,8 +207,10 @@ private void onDealText(){ @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); - setMeasuredDimension(mHorizontalPadding * 2 + (int) fontW, - mVerticalPadding * 2 + (int) fontH); + 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 @@ -139,29 +221,49 @@ protected void onSizeChanged(int w, int h, int oldw, int oldh) { @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); - if (mTextDirection == View.TEXT_DIRECTION_RTL){ - float tmpX = 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); + 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, getWidth() / 2 - fontW / 2, + } 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 @@ -172,15 +274,19 @@ public boolean dispatchTouchEvent(MotionEvent event) { int action = event.getAction(); switch (action){ case MotionEvent.ACTION_DOWN: - getParent().requestDisallowInterceptTouchEvent(true); + if (getParent() != null) { + getParent().requestDisallowInterceptTouchEvent(true); + } mLastY = y; mLastX = x; break; case MotionEvent.ACTION_MOVE: - if (Math.abs(mLastY - y) > mSlopThreshold - || Math.abs(mLastX - x) > mSlopThreshold){ - getParent().requestDisallowInterceptTouchEvent(false); + if (!isViewSelected && (Math.abs(mLastY - y) > mSlopThreshold + || Math.abs(mLastX - x) > mSlopThreshold)){ + if (getParent() != null) { + getParent().requestDisallowInterceptTouchEvent(false); + } isMoved = true; return false; } @@ -192,10 +298,21 @@ public boolean dispatchTouchEvent(MotionEvent 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; @@ -212,6 +329,9 @@ 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; @@ -227,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; } @@ -235,6 +456,10 @@ public boolean getIsViewClickable(){ return isViewClickable; } + public boolean getIsViewSelected(){ + return isViewSelected; + } + public void setTagMaxLength(int maxLength){ this.mTagMaxLength = maxLength; onDealText(); @@ -244,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; } @@ -281,9 +518,35 @@ 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() { @@ -299,7 +562,69 @@ public void setTypeface(Typeface 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 40fbb25..098f9a9 100644 --- a/androidtagview/src/main/res/values/attrs.xml +++ b/androidtagview/src/main/res/values/attrs.xml @@ -10,6 +10,7 @@ + @@ -27,6 +28,7 @@ + @@ -37,5 +39,17 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/build.gradle b/build.gradle index c03d0ad..a115e91 100644 --- a/build.gradle +++ b/build.gradle @@ -3,11 +3,12 @@ buildscript { repositories { jcenter() + google() } 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' + 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 @@ -17,6 +18,7 @@ buildscript { allprojects { repositories { jcenter() + google() } } diff --git a/gradle.properties b/gradle.properties index 629cc7b..a565221 100644 --- a/gradle.properties +++ b/gradle.properties @@ -13,3 +13,5 @@ # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects # 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 6216523..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 -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 +#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-4.9-all.zip diff --git a/sample/build.gradle b/sample/build.gradle index 85106e2..db7ac6a 100644 --- a/sample/build.gradle +++ b/sample/build.gradle @@ -1,13 +1,12 @@ apply plugin: 'com.android.application' android { - compileSdkVersion 23 - buildToolsVersion "22.0.1" + compileSdkVersion 28 defaultConfig { applicationId "co.lujun.sample" - minSdkVersion 9 - targetSdkVersion 23 + minSdkVersion 14 + targetSdkVersion 28 versionCode 1 versionName "1.0" } @@ -19,10 +18,16 @@ android { } } +repositories { + mavenCentral() + maven { url 'https://maven.google.com' } +} + 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' + 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/src/main/AndroidManifest.xml b/sample/src/main/AndroidManifest.xml index b96d3e8..e56e5f9 100644 --- a/sample/src/main/AndroidManifest.xml +++ b/sample/src/main/AndroidManifest.xml @@ -19,5 +19,5 @@ - + diff --git a/sample/src/main/java/co/lujun/sample/MainActivity.java b/sample/src/main/java/co/lujun/sample/MainActivity.java index d4206e5..9399ad4 100644 --- a/sample/src/main/java/co/lujun/sample/MainActivity.java +++ b/sample/src/main/java/co/lujun/sample/MainActivity.java @@ -1,25 +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.Typeface; +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, mTagContainerLayout4; + private TagContainerLayout mTagContainerLayout1, mTagContainerLayout2, + mTagContainerLayout3, mTagContainerLayout4, mTagcontainerLayout5; @Override protected void onCreate(Bundle savedInstanceState) { @@ -57,10 +69,18 @@ protected void onCreate(Bundle savedInstanceState) { 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 @@ -112,10 +183,20 @@ public void onClick(DialogInterface dialog, int which) { // 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() { @@ -126,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 a440a0b..12311a8 100644 --- a/sample/src/main/res/layout/activity_main.xml +++ b/sample/src/main/res/layout/activity_main.xml @@ -1,5 +1,5 @@ - - - - + - + diff --git a/sample/src/main/res/layout/content_main.xml b/sample/src/main/res/layout/content_main.xml index ccb9b38..bb89e68 100644 --- a/sample/src/main/res/layout/content_main.xml +++ b/sample/src/main/res/layout/content_main.xml @@ -13,6 +13,12 @@ tools:context="co.lujun.sample.MainActivity" tools:showIn="@layout/activity_main"> + + @@ -31,6 +37,7 @@ app:container_enable_drag="true" app:horizontal_interval="10dp" app:tag_clickable="true" + app:tag_enable_cross="true" app:tag_theme="pure_teal" app:vertical_interval="10dp" /> @@ -80,13 +87,15 @@ app:container_border_radius="0dp" app:container_gravity="right" app:horizontal_interval="10dp" - app:tag_background_color="#00000000" + app:tag_background_color="#FFFFFFFF" app:tag_border_color="#330000ff" app:tag_border_width="1dp" - app:tag_clickable="false" + app:tag_clickable="true" app:tag_corner_radius="1dp" + app:tag_enable_cross="true" app:tag_horizontal_padding="15dp" app:tag_max_length="18" + app:tag_selectable="true" app:tag_text_color="#ff666666" app:tag_text_direction="rtl" app:tag_text_size="14sp" @@ -104,9 +113,24 @@ app:container_gravity="center" app:horizontal_interval="10dp" app:tag_clickable="true" + app:tag_ripple_alpha="47" + app:tag_ripple_color="#000000" + app:tag_ripple_duration="2000" app:tag_theme="pure_teal" app:vertical_interval="10dp" /> + diff --git a/sample/src/main/res/layout/view_recyclerview_item.xml b/sample/src/main/res/layout/view_recyclerview_item.xml new file mode 100644 index 0000000..b499e4c --- /dev/null +++ b/sample/src/main/res/layout/view_recyclerview_item.xml @@ -0,0 +1,24 @@ + + + + + +