├── .gitignore ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── lcodecore │ │ └── labellayout │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── lcodecore │ │ │ └── labellayout │ │ │ └── MainActivity.java │ └── res │ │ ├── layout │ │ └── activity_main.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ └── values │ │ ├── colors.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── com │ └── lcodecore │ └── labellayout │ └── ExampleUnitTest.java ├── art ├── image0.png ├── image1.jpg └── image2.jpg ├── build.gradle ├── labellibrary ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── lcodecore │ │ ├── ILabel.java │ │ └── LabelLayout.java │ └── res │ ├── drawable │ ├── label_gray_selector.xml │ └── label_selector.xml │ ├── layout │ └── view_label_common.xml │ └── values │ ├── attrs.xml │ ├── strings.xml │ └── styles.xml └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea 5 | /gradle 6 | /gradle.properties 7 | /gradlew 8 | /gradlew.bat 9 | .DS_Store 10 | /build 11 | /captures 12 | .externalNativeBuild 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## 最简洁的标签(label/tag)选择/展示控件----LabelLayout 2 | LabelLayout只是用于标签布局(layout),搭配CheckBox使用,标签样式可以随意更换,完全没有束缚,小而精悍。(支持API 9 及以上) 3 | 4 | ### Demo图 5 | ![](art/image0.png) ![](art/image1.jpg) ![](art/image2.jpg) 6 | 7 | ### 属性 8 | - label_horizontalSpacing 水平方向上标签之间的距离 9 | - label_verticalSpacing 竖直方向上标签之间的距离 10 | - label_checkboxLayout 传入CheckBox布局(也就是标签的样式) 11 | - label_enableDivider 是否需要在标签行间加分割线,默认为**false** 12 | - label_dividerHeight 行间分割线的高度 13 | - label_dividerColor 行间分割线的颜色 14 | 15 | ### 使用 16 | #### 添加gradle依赖 17 | ``` 18 | compile 'com.lcodecorex:labellayout:1.0.1' 19 | ``` 20 | 21 | #### 方法及回调 22 | 1. 定义标签数据Bean实现ILabel接口 23 | ```java 24 | public interface ILabel { 25 | //warning! Ids should be unique!!! 26 | public String getId(); 27 | public String getName(); 28 | } 29 | ``` 30 | 注意getId()放回的id值必须是独特的,不可重复。 31 | 32 | 2. layout布局 33 | ```xml 34 | 44 | ``` 45 | 46 | 2. 设置标签,使用setLabels()方法 47 | ```java 48 | labelLayout.setLabels(List labels); 49 | ``` 50 | 51 | 3. 添加标签选择监听器 52 | ```java 53 | label_me.setOnCheckChangedListener(new LabelLayout.OnCheckListener() { 54 | @Override 55 | public void onCheckChanged(ILabel label,boolean isChecked) {} 56 | 57 | @Override 58 | public void onBeyondMaxCheckCount() { 59 | 60 | } 61 | }); 62 | ``` 63 | 64 | 4. 如果是需要选择标签,可以调用setMaxCheckCount(int)方法,选中的标签超过这个数checkListener的onBeyondMaxCheckCount()方法会被回调。 65 | 66 | 6. 其它方法 67 | - getCheckedLabelsCount 获取当前被选中的标签数量 68 | - getCheckedLabelIds() 获取被选中的标签的id列表 69 | - getCheckedIdsAsJson() 获取json序列化之后的id的json串 70 | 71 | #### 个性化(可参考默认样式:view_label_common.xml) 72 | --- 主布局 73 | ```xml 74 | 85 | ``` 86 | 87 | --- checkbox_gray.xml 88 | ```xml 89 | 90 | 102 | ``` 103 | 104 | --- 图一中用的CheckBox样式 105 | ```xml 106 | 107 | 115 | ``` 116 | 117 | > ps 118 | > Contact me:lcodecore@163.com 119 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 25 5 | buildToolsVersion "25.0.1" 6 | defaultConfig { 7 | applicationId "com.lcodecore.labellayout" 8 | minSdkVersion 14 9 | targetSdkVersion 25 10 | versionCode 1 11 | versionName "1.0" 12 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 13 | } 14 | buildTypes { 15 | release { 16 | minifyEnabled false 17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 18 | } 19 | } 20 | lintOptions { 21 | abortOnError false 22 | } 23 | } 24 | 25 | dependencies { 26 | compile fileTree(dir: 'libs', include: ['*.jar']) 27 | androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { 28 | exclude group: 'com.android.support', module: 'support-annotations' 29 | }) 30 | compile 'com.android.support:appcompat-v7:25.2.0' 31 | compile 'com.android.support.constraint:constraint-layout:1.0.2' 32 | testCompile 'junit:junit:4.12' 33 | } 34 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in C:\Users\lcodecore\Applications\Android\sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | 19 | # Uncomment this to preserve the line number information for 20 | # debugging stack traces. 21 | #-keepattributes SourceFile,LineNumberTable 22 | 23 | # If you keep the line number information, uncomment this to 24 | # hide the original source file name. 25 | #-renamesourcefileattribute SourceFile 26 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/lcodecore/labellayout/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.lcodecore.labellayout; 2 | 3 | import android.content.Context; 4 | import android.support.test.InstrumentationRegistry; 5 | import android.support.test.runner.AndroidJUnit4; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | 10 | import static org.junit.Assert.*; 11 | 12 | /** 13 | * Instrumentation test, which will execute on an Android device. 14 | * 15 | * @see Testing documentation 16 | */ 17 | @RunWith(AndroidJUnit4.class) 18 | public class ExampleInstrumentedTest { 19 | @Test 20 | public void useAppContext() throws Exception { 21 | // Context of the app under test. 22 | Context appContext = InstrumentationRegistry.getTargetContext(); 23 | 24 | assertEquals("com.lcodecore.labellayout", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /app/src/main/java/com/lcodecore/labellayout/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.lcodecore.labellayout; 2 | 3 | import android.support.v7.app.AppCompatActivity; 4 | import android.os.Bundle; 5 | 6 | public class MainActivity extends AppCompatActivity { 7 | 8 | @Override 9 | protected void onCreate(Bundle savedInstanceState) { 10 | super.onCreate(savedInstanceState); 11 | setContentView(R.layout.activity_main); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lcodecorex/LabelLayout/835120003b0973415dbb56b68da9add9624a595c/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lcodecorex/LabelLayout/835120003b0973415dbb56b68da9add9624a595c/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lcodecorex/LabelLayout/835120003b0973415dbb56b68da9add9624a595c/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lcodecorex/LabelLayout/835120003b0973415dbb56b68da9add9624a595c/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lcodecorex/LabelLayout/835120003b0973415dbb56b68da9add9624a595c/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lcodecorex/LabelLayout/835120003b0973415dbb56b68da9add9624a595c/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lcodecorex/LabelLayout/835120003b0973415dbb56b68da9add9624a595c/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lcodecorex/LabelLayout/835120003b0973415dbb56b68da9add9624a595c/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lcodecorex/LabelLayout/835120003b0973415dbb56b68da9add9624a595c/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lcodecorex/LabelLayout/835120003b0973415dbb56b68da9add9624a595c/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #3F51B5 4 | #303F9F 5 | #FF4081 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | LabelLayout 3 | 4 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/test/java/com/lcodecore/labellayout/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package com.lcodecore.labellayout; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.*; 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * @see Testing documentation 11 | */ 12 | public class ExampleUnitTest { 13 | @Test 14 | public void addition_isCorrect() throws Exception { 15 | assertEquals(4, 2 + 2); 16 | } 17 | } -------------------------------------------------------------------------------- /art/image0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lcodecorex/LabelLayout/835120003b0973415dbb56b68da9add9624a595c/art/image0.png -------------------------------------------------------------------------------- /art/image1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lcodecorex/LabelLayout/835120003b0973415dbb56b68da9add9624a595c/art/image1.jpg -------------------------------------------------------------------------------- /art/image2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lcodecorex/LabelLayout/835120003b0973415dbb56b68da9add9624a595c/art/image2.jpg -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | repositories { 5 | jcenter() 6 | } 7 | dependencies { 8 | classpath 'com.android.tools.build:gradle:2.3.0' 9 | classpath 'com.novoda:bintray-release:0.3.4' 10 | // NOTE: Do not place your application dependencies here; they belong 11 | // in the individual module build.gradle files 12 | } 13 | } 14 | 15 | allprojects { 16 | repositories { 17 | jcenter() 18 | } 19 | tasks.withType(Javadoc) { 20 | options.addStringOption('Xdoclint:none', '-quiet') 21 | options.addStringOption('encoding', 'UTF-8') 22 | } 23 | } 24 | 25 | task clean(type: Delete) { 26 | delete rootProject.buildDir 27 | } 28 | -------------------------------------------------------------------------------- /labellibrary/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /labellibrary/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | apply plugin: 'com.novoda.bintray-release' 3 | 4 | publish { 5 | userOrg = 'lcodecorex' 6 | groupId = 'com.lcodecorex' 7 | artifactId = 'labellayout' 8 | publishVersion = '1.0.1' 9 | website = 'https://github.com/lcodecorex/LabelLayout' 10 | } 11 | 12 | android { 13 | compileSdkVersion 25 14 | buildToolsVersion "25.0.1" 15 | 16 | defaultConfig { 17 | minSdkVersion 9 18 | targetSdkVersion 25 19 | versionCode 1 20 | versionName "1.0" 21 | } 22 | buildTypes { 23 | release { 24 | minifyEnabled false 25 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 26 | } 27 | } 28 | lintOptions { 29 | abortOnError false 30 | } 31 | } 32 | 33 | dependencies { 34 | compile fileTree(dir: 'libs', include: ['*.jar']) 35 | } 36 | -------------------------------------------------------------------------------- /labellibrary/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in C:\Users\lcodecore\Applications\Android\sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | 19 | # Uncomment this to preserve the line number information for 20 | # debugging stack traces. 21 | #-keepattributes SourceFile,LineNumberTable 22 | 23 | # If you keep the line number information, uncomment this to 24 | # hide the original source file name. 25 | #-renamesourcefileattribute SourceFile 26 | -------------------------------------------------------------------------------- /labellibrary/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /labellibrary/src/main/java/com/lcodecore/ILabel.java: -------------------------------------------------------------------------------- 1 | package com.lcodecore; 2 | 3 | public interface ILabel { 4 | //warning! Ids should be unique!!! 5 | public String getId(); 6 | public String getName(); 7 | } -------------------------------------------------------------------------------- /labellibrary/src/main/java/com/lcodecore/LabelLayout.java: -------------------------------------------------------------------------------- 1 | package com.lcodecore; 2 | 3 | import android.content.Context; 4 | import android.content.res.TypedArray; 5 | import android.graphics.Canvas; 6 | import android.graphics.Paint; 7 | import android.util.AttributeSet; 8 | import android.util.TypedValue; 9 | import android.view.View; 10 | import android.view.ViewGroup; 11 | import android.widget.CheckBox; 12 | import android.widget.CompoundButton; 13 | 14 | import com.lcodecore.labellibrary.R; 15 | 16 | import org.json.JSONArray; 17 | 18 | import java.util.ArrayList; 19 | import java.util.HashMap; 20 | import java.util.HashSet; 21 | import java.util.List; 22 | import java.util.Map; 23 | import java.util.Set; 24 | 25 | /** 26 | * A LabelLayout ViewGroup that can hold labels with the help of CheckBox. 27 | * 配合CheckBox使用,可设置允许的最大标签选择数量 28 | */ 29 | public class LabelLayout extends ViewGroup { 30 | 31 | private int mMaxCheckCount = Integer.MAX_VALUE; 32 | /** 33 | * 竖直方向间距, default is 8.0dp. 34 | */ 35 | private int horizontalSpacing; 36 | 37 | /** 38 | * 水平方向间距, default is 4.0dp. 39 | */ 40 | private int verticalSpacing; 41 | 42 | //whether or not to draw the divider between labels at horizon. 43 | private boolean enableDivider = false; //是否允许显示分割线 默认不显示 44 | private int dividerColor = 0xffECECEC; 45 | private float dividerHeight; 46 | 47 | private int checkboxLayoutId; 48 | 49 | //nark checked labels. 50 | private Map labelcheckMap; 51 | //mark the first position in a row. 52 | private Set rowPositons = new HashSet<>(); 53 | 54 | //The paint to draw the divider. 55 | Paint dividerPaint; 56 | 57 | public LabelLayout(Context context) { 58 | this(context, null); 59 | } 60 | 61 | public LabelLayout(Context context, AttributeSet attrs) { 62 | this(context, attrs, 0); 63 | } 64 | 65 | public LabelLayout(Context context, AttributeSet attrs, int defStyleAttr) { 66 | super(context, attrs, defStyleAttr); 67 | setWillNotDraw(false); 68 | 69 | labelcheckMap = new HashMap<>(); 70 | 71 | final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.LabelLayout, defStyleAttr, R.style.LabelLayoutDefault); 72 | try { 73 | horizontalSpacing = (int) a.getDimension(R.styleable.LabelLayout_label_horizontalSpacing, dp2px(8.0f)); 74 | verticalSpacing = (int) a.getDimension(R.styleable.LabelLayout_label_verticalSpacing, dp2px(4.0f)); 75 | 76 | checkboxLayoutId = a.getResourceId(R.styleable.LabelLayout_label_checkboxLayout, R.layout.view_label_common); 77 | enableDivider = a.getBoolean(R.styleable.LabelLayout_label_enableDivider, false); 78 | dividerHeight = a.getDimension(R.styleable.LabelLayout_label_dividerHeight, dp2px(2)); 79 | dividerColor = a.getColor(R.styleable.LabelLayout_label_dividerColor, 0xffECECEC); 80 | } finally { 81 | a.recycle(); 82 | } 83 | } 84 | 85 | @Override 86 | protected void onDraw(Canvas canvas) { 87 | super.onDraw(canvas); 88 | if (enableDivider) { 89 | if (dividerPaint == null) { 90 | dividerPaint = new Paint(); 91 | dividerPaint.setAntiAlias(true); 92 | dividerPaint.setColor(dividerColor); 93 | dividerPaint.setStyle(Paint.Style.FILL); 94 | } 95 | 96 | for (Integer top : rowPositons) { 97 | if (top != 0) { 98 | //draw lines between labels. 99 | canvas.drawRect(0, top - dividerHeight / 2, getMeasuredWidth(), top + dividerHeight / 2, dividerPaint); 100 | } 101 | } 102 | } 103 | } 104 | 105 | @Override 106 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 107 | final int widthMode = MeasureSpec.getMode(widthMeasureSpec); 108 | final int heightMode = MeasureSpec.getMode(heightMeasureSpec); 109 | final int widthSize = MeasureSpec.getSize(widthMeasureSpec); 110 | final int heightSize = MeasureSpec.getSize(heightMeasureSpec); 111 | 112 | measureChildren(widthMeasureSpec, heightMeasureSpec); 113 | 114 | int width = 0; 115 | int height = 0; 116 | 117 | int row = 0; // The row counter. 118 | int rowWidth = 0; // Calc the current row width. 119 | int rowMaxHeight = 0; // Calc the max tag height, in current row. 120 | 121 | final int count = getChildCount(); 122 | for (int i = 0; i < count; i++) { 123 | final View child = getChildAt(i); 124 | final int childWidth = child.getMeasuredWidth(); 125 | final int childHeight = child.getMeasuredHeight(); 126 | 127 | if (child.getVisibility() != GONE) { 128 | rowWidth += childWidth; 129 | if (rowWidth > widthSize) { // Next line. 130 | rowWidth = childWidth; // The next row width. 131 | height += rowMaxHeight + verticalSpacing; 132 | rowMaxHeight = childHeight; // The next row max height. 133 | row++; 134 | } else { // This line. 135 | rowMaxHeight = Math.max(rowMaxHeight, childHeight); 136 | } 137 | rowWidth += horizontalSpacing; 138 | } 139 | // System.out.println("measured height:" + height); 140 | rowPositons.add(height - verticalSpacing / 2); 141 | } 142 | // Account for the last row height. 143 | height += rowMaxHeight; 144 | 145 | // Account for the padding too. 146 | height += getPaddingTop() + getPaddingBottom(); 147 | 148 | // If the tags grouped in one row, set the width to wrap the tags. 149 | if (row == 0) { 150 | width = rowWidth; 151 | width += getPaddingLeft() + getPaddingRight(); 152 | } else {// If the tags grouped exceed one line, set the width to match the parent. 153 | width = widthSize; 154 | } 155 | 156 | setMeasuredDimension(widthMode == MeasureSpec.EXACTLY ? widthSize : width, 157 | heightMode == MeasureSpec.EXACTLY ? heightSize : height); 158 | } 159 | 160 | @Override 161 | protected void onLayout(boolean changed, int l, int t, int r, int b) { 162 | final int parentLeft = getPaddingLeft(); 163 | final int parentRight = r - l - getPaddingRight(); 164 | final int parentTop = getPaddingTop(); 165 | final int parentBottom = b - t - getPaddingBottom(); 166 | 167 | int childLeft = parentLeft; 168 | int childTop = parentTop; 169 | 170 | int rowMaxHeight = 0; 171 | 172 | final int count = getChildCount(); 173 | for (int i = 0; i < count; i++) { 174 | final View child = getChildAt(i); 175 | final int width = child.getMeasuredWidth(); 176 | final int height = child.getMeasuredHeight(); 177 | 178 | if (child.getVisibility() != GONE) { 179 | if (childLeft + width > parentRight) { // Next line 180 | childLeft = parentLeft; 181 | childTop += rowMaxHeight + verticalSpacing; 182 | rowMaxHeight = height; 183 | } else { 184 | rowMaxHeight = Math.max(rowMaxHeight, height); 185 | } 186 | child.layout(childLeft, childTop, childLeft + width, childTop + height); 187 | 188 | childLeft += width + horizontalSpacing; 189 | } 190 | } 191 | } 192 | 193 | /** 194 | * set the default labels that you wanna to add into LabelLayout. 195 | * 196 | * @param labels A collection contains objects that implement ILabel interface. 197 | */ 198 | public void setLabels(List labels) { 199 | labelcheckMap.clear(); 200 | removeAllViews(); 201 | if (labels == null || labels.size() == 0) return; 202 | for (final ILabel label : labels) { 203 | final CheckBox tagView = (CheckBox) View.inflate(getContext(), checkboxLayoutId, null); 204 | tagView.setText(label.getName()); 205 | addView(tagView); 206 | tagView.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { 207 | @Override 208 | public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { 209 | if (isChecked) {//选中时添加到map 210 | if (labelcheckMap.size() > mMaxCheckCount - 1) { 211 | if (checkListener != null) { 212 | checkListener.onBeyondMaxCheckCount(); 213 | } 214 | tagView.setChecked(false); 215 | } else { 216 | labelcheckMap.put(label.getId(), true); 217 | if (checkListener != null) { 218 | checkListener.onCheckChanged(label, true); 219 | } 220 | } 221 | } else {//否则及时清理map 222 | if (labelcheckMap.containsKey(label.getId())) { 223 | labelcheckMap.remove(label.getId()); 224 | if (checkListener != null) { 225 | checkListener.onCheckChanged(label, false); 226 | } 227 | } 228 | } 229 | } 230 | }); 231 | } 232 | } 233 | 234 | /** 235 | * set the maximum numbers of checked labels. 236 | */ 237 | public void setMaxCheckCount(int count) { 238 | mMaxCheckCount = count; 239 | } 240 | 241 | /** 242 | * Get the current selected tag number. 243 | */ 244 | public int getCheckedLabelsCount() { 245 | int count = 0; 246 | for (Map.Entry m : labelcheckMap.entrySet()) { 247 | if (m.getValue()) { 248 | count++; 249 | } 250 | } 251 | return count; 252 | } 253 | 254 | public List getCheckedLabelIds() { 255 | List chechedLabelIds = new ArrayList<>(); 256 | for (Map.Entry m : labelcheckMap.entrySet()) { 257 | if (m.getValue()) { 258 | chechedLabelIds.add(m.getKey()); 259 | } 260 | } 261 | return chechedLabelIds; 262 | } 263 | 264 | /** 265 | * To serialize checked-label ids as json, make benefit for use. 266 | * 267 | * @return json string 268 | */ 269 | public String getCheckedIdsAsJson() { 270 | List chechedId = new ArrayList<>(); 271 | for (Map.Entry m : labelcheckMap.entrySet()) { 272 | if (m.getValue()) { 273 | chechedId.add(m.getKey()); 274 | } 275 | } 276 | return new JSONArray(chechedId).toString(); 277 | } 278 | 279 | 280 | private float dp2px(float dp) { 281 | return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, 282 | getResources().getDisplayMetrics()); 283 | } 284 | 285 | private OnCheckChangeListener checkListener; 286 | 287 | public interface OnCheckChangeListener { 288 | void onCheckChanged(ILabel label, boolean isChecked); 289 | 290 | void onBeyondMaxCheckCount(); 291 | } 292 | 293 | public void setOnCheckChangedListener(OnCheckChangeListener checkListener) { 294 | this.checkListener = checkListener; 295 | } 296 | 297 | } 298 | -------------------------------------------------------------------------------- /labellibrary/src/main/res/drawable/label_gray_selector.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /labellibrary/src/main/res/drawable/label_selector.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /labellibrary/src/main/res/layout/view_label_common.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /labellibrary/src/main/res/values/attrs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /labellibrary/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | LabelLibrary 3 | 4 | -------------------------------------------------------------------------------- /labellibrary/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app', ':labellibrary' 2 | --------------------------------------------------------------------------------