Android 自定义标签
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 | /** * Created by tab on 2020/4/30. */ public class TabLabelsView extends ViewGroup implements View.OnClickListener { private Context mContext; private ColorStateList mTextColor; private float mTextSize; private Drawable mLabelBg; private int mTextPaddingLeft; private int mTextPaddingTop; private int mTextPaddingRight; private int mTextPaddingBottom; private int mWordMargin; private int mLineMargin; private SelectType mSelectType; private int mMaxSelect; private int mMinSelect; private int mMaxLines; private boolean isIndicator; //只能看,不能手动改变选中状态。 //用于保存label数据的key private static final int KEY_DATA = R.id.tag_key_data; //用于保存label位置的key private static final int KEY_POSITION = R.id.tag_key_position; private ArrayList<Object> mLabels = new ArrayList<>(); //保存选中的label的位置 private ArrayList<Integer> mSelectLabels = new ArrayList<>(); //保存必选项。在多选模式下,可以设置必选项,必选项默认选中,不能反选 private ArrayList<Integer> mCompulsorys = new ArrayList<>(); private OnLabelClickListener mLabelClickListener; private OnLabelSelectChangeListener mLabelSelectChangeListener; private ArrayList<TextView> labelsTxt = new ArrayList<>(); /** * Label的选择类型 */ public enum SelectType { //不可选中,也不响应选中事件回调。(默认) NONE( 1 ), //单选,可以反选。 SINGLE( 2 ), //单选,不可以反选。这种模式下,至少有一个是选中的,默认是第一个 SINGLE_IRREVOCABLY( 3 ), //多选 MULTI( 4 ); int value; SelectType( int value) { this .value = value; } static SelectType get( int value) { switch (value) { case 1 : return NONE; case 2 : return SINGLE; case 3 : return SINGLE_IRREVOCABLY; case 4 : return MULTI; } return NONE; } } public TabLabelsView(Context context) { super (context); mContext = context; } public TabLabelsView(Context context, AttributeSet attrs) { super (context, attrs); mContext = context; getAttrs(context, attrs); } public TabLabelsView(Context context, AttributeSet attrs, int defStyleAttr) { super (context, attrs, defStyleAttr); mContext = context; getAttrs(context, attrs); } private void getAttrs(Context context, AttributeSet attrs) { if (attrs != null ) { TypedArray mTypedArray = context.obtainStyledAttributes(attrs, R.styleable.tab_labels_view); int type = mTypedArray.getInt(R.styleable.tab_labels_view_selectType, 1 ); mSelectType = SelectType.get(type); mMaxSelect = mTypedArray.getInteger(R.styleable.tab_labels_view_maxSelect, 0 ); mMinSelect = mTypedArray.getInteger(R.styleable.tab_labels_view_minSelect, 0 ); mMaxLines = mTypedArray.getInteger(R.styleable.tab_labels_view_maxLines, 0 ); isIndicator = mTypedArray.getBoolean(R.styleable.tab_labels_view_isIndicator, false ); mTextColor = mTypedArray.getColorStateList(R.styleable.tab_labels_view_labelTextColor); mTextSize = mTypedArray.getDimension(R.styleable.tab_labels_view_labelTextSize, sp2px(context, 14 )); mTextPaddingLeft = mTypedArray.getDimensionPixelOffset( R.styleable.tab_labels_view_labelTextPaddingLeft, 0 ); mTextPaddingTop = mTypedArray.getDimensionPixelOffset( R.styleable.tab_labels_view_labelTextPaddingTop, 0 ); mTextPaddingRight = mTypedArray.getDimensionPixelOffset( R.styleable.tab_labels_view_labelTextPaddingRight, 0 ); mTextPaddingBottom = mTypedArray.getDimensionPixelOffset( R.styleable.tab_labels_view_labelTextPaddingBottom, 0 ); mLineMargin = mTypedArray.getDimensionPixelOffset(R.styleable.tab_labels_view_lineMargin, 0 ); mWordMargin = mTypedArray.getDimensionPixelOffset(R.styleable.tab_labels_view_wordMargin, 0 ); int labelBgResId = mTypedArray.getResourceId(R.styleable.tab_labels_view_labelBackground, 0 ); if (labelBgResId != 0 ) { mLabelBg = getResources().getDrawable(labelBgResId); } else { int labelBgColor = mTypedArray.getColor(R.styleable.tab_labels_view_labelBackground, Color.TRANSPARENT); mLabelBg = new ColorDrawable(labelBgColor); } mTypedArray.recycle(); } } @Override protected void onMeasure( int widthMeasureSpec, int heightMeasureSpec) { int count = getChildCount(); int maxWidth = MeasureSpec.getSize(widthMeasureSpec) - getPaddingLeft() - getPaddingRight(); int contentHeight = 0 ; //记录内容的高度 int lineWidth = 0 ; //记录行的宽度 int maxLineWidth = 0 ; //记录最宽的行宽 int maxItemHeight = 0 ; //记录一行中item高度最大的高度 int lineCount = 1 ; for ( int i = 0 ; i < count; i++) { View view = getChildAt(i); measureChild(view, widthMeasureSpec, heightMeasureSpec); if (lineWidth + view.getMeasuredWidth() > maxWidth) { lineCount++; if (mMaxLines > 0 && lineCount > mMaxLines) { break ; } contentHeight += mLineMargin; contentHeight += maxItemHeight; maxItemHeight = 0 ; maxLineWidth = Math.max(maxLineWidth, lineWidth); lineWidth = 0 ; } lineWidth += view.getMeasuredWidth(); maxItemHeight = Math.max(maxItemHeight, view.getMeasuredHeight()); if (i != count - 1 ) { if (lineWidth + mWordMargin > maxWidth) { // 换行 lineCount++; if (mMaxLines > 0 && lineCount > mMaxLines) { break ; } contentHeight += mLineMargin; contentHeight += maxItemHeight; maxItemHeight = 0 ; maxLineWidth = Math.max(maxLineWidth, lineWidth); lineWidth = 0 ; } else { lineWidth += mWordMargin; } } } contentHeight += maxItemHeight; maxLineWidth = Math.max(maxLineWidth, lineWidth); setMeasuredDimension(measureWidth(widthMeasureSpec, maxLineWidth), measureHeight(heightMeasureSpec, contentHeight)); } private int measureWidth( int measureSpec, int contentWidth) { int result = 0 ; int specMode = MeasureSpec.getMode(measureSpec); int specSize = MeasureSpec.getSize(measureSpec); if (specMode == MeasureSpec.EXACTLY) { result = specSize; } else { result = contentWidth + getPaddingLeft() + getPaddingRight(); if (specMode == MeasureSpec.AT_MOST) { result = Math.min(result, specSize); } } result = Math.max(result, getSuggestedMinimumWidth()); return result; } private int measureHeight( int measureSpec, int contentHeight) { int result = 0 ; int specMode = MeasureSpec.getMode(measureSpec); int specSize = MeasureSpec.getSize(measureSpec); if (specMode == MeasureSpec.EXACTLY) { result = specSize; } else { result = contentHeight + getPaddingTop() + getPaddingBottom(); if (specMode == MeasureSpec.AT_MOST) { result = Math.min(result, specSize); } } result = Math.max(result, getSuggestedMinimumHeight()); return result; } @Override protected void onLayout( boolean changed, int left, int top, int right, int bottom) { int x = getPaddingLeft(); int y = getPaddingTop(); int contentWidth = right - left; int maxItemHeight = 0 ; int lineCount = 1 ; int count = getChildCount(); for ( int i = 0 ; i < count; i++) { View view = getChildAt(i); if (contentWidth < x + view.getMeasuredWidth() + getPaddingRight()) { lineCount++; if (mMaxLines > 0 && lineCount > mMaxLines) { break ; } x = getPaddingLeft(); y += mLineMargin; y += maxItemHeight; maxItemHeight = 0 ; } view.layout(x, y, x + view.getMeasuredWidth(), y + view.getMeasuredHeight()); x += view.getMeasuredWidth(); x += mWordMargin; maxItemHeight = Math.max(maxItemHeight, view.getMeasuredHeight()); } } /* 用于保存View的信息的key */ private static final String KEY_SUPER_STATE = "key_super_state" ; private static final String KEY_TEXT_COLOR_STATE = "key_text_color_state" ; private static final String KEY_TEXT_SIZE_STATE = "key_text_size_state" ; private static final String KEY_BG_RES_ID_STATE = "key_bg_res_id_state" ; private static final String KEY_PADDING_STATE = "key_padding_state" ; private static final String KEY_WORD_MARGIN_STATE = "key_word_margin_state" ; private static final String KEY_LINE_MARGIN_STATE = "key_line_margin_state" ; private static final String KEY_SELECT_TYPE_STATE = "key_select_type_state" ; private static final String KEY_MAX_SELECT_STATE = "key_max_select_state" ; private static final String KEY_MIN_SELECT_STATE = "key_min_select_state" ; private static final String KEY_MAX_LINES_STATE = "key_max_lines_state" ; private static final String KEY_INDICATOR_STATE = "key_indicator_state" ; // 由于新版(1.4.0)的标签列表允许设置任何类型的数据,而不仅仅是String。并且标签显示的内容 // 最终由LabelTextProvider提供,所以LabelsView不再在onSaveInstanceState()和onRestoreInstanceState() // 中保存和恢复标签列表的数据。 private static final String KEY_LABELS_STATE = "key_labels_state" ; private static final String KEY_SELECT_LABELS_STATE = "key_select_labels_state" ; private static final String KEY_COMPULSORY_LABELS_STATE = "key_select_compulsory_state" ; @Override protected Parcelable onSaveInstanceState() { Bundle bundle = new Bundle(); //保存父类的信息 bundle.putParcelable(KEY_SUPER_STATE, super .onSaveInstanceState()); //保存标签文字颜色 if (mTextColor != null ) { bundle.putParcelable(KEY_TEXT_COLOR_STATE, mTextColor); } //保存标签文字大小 bundle.putFloat(KEY_TEXT_SIZE_STATE, mTextSize); //保存标签背景 (由于背景改用Drawable,所以不能自动保存和恢复) // bundle.putInt(KEY_BG_RES_ID_STATE, mLabelBgResId); //保存标签内边距 bundle.putIntArray(KEY_PADDING_STATE, new int []{mTextPaddingLeft, mTextPaddingTop, mTextPaddingRight, mTextPaddingBottom}); //保存标签间隔 bundle.putInt(KEY_WORD_MARGIN_STATE, mWordMargin); //保存行间隔 bundle.putInt(KEY_LINE_MARGIN_STATE, mLineMargin); //保存标签的选择类型 bundle.putInt(KEY_SELECT_TYPE_STATE, mSelectType.value); //保存标签的最大选择数量 bundle.putInt(KEY_MAX_SELECT_STATE, mMaxSelect); //保存标签的最少选择数量 bundle.putInt(KEY_MIN_SELECT_STATE, mMinSelect); //保存标签的最大行数 bundle.putInt(KEY_MAX_LINES_STATE, mMaxLines); //保存是否是指示器模式 bundle.putBoolean(KEY_INDICATOR_STATE, isIndicator); //保存标签列表 // if (!mLabels.isEmpty()) { // bundle.putStringArrayList(KEY_LABELS_STATE, mLabels); // } //保存已选择的标签列表 if (!mSelectLabels.isEmpty()) { bundle.putIntegerArrayList(KEY_SELECT_LABELS_STATE, mSelectLabels); } //保存必选项列表 if (!mCompulsorys.isEmpty()) { bundle.putIntegerArrayList(KEY_COMPULSORY_LABELS_STATE, mCompulsorys); } return bundle; } @Override protected void onRestoreInstanceState(Parcelable state) { if (state instanceof Bundle) { Bundle bundle = (Bundle) state; //恢复父类信息 super .onRestoreInstanceState(bundle.getParcelable(KEY_SUPER_STATE)); //恢复标签文字颜色 ColorStateList color = bundle.getParcelable(KEY_TEXT_COLOR_STATE); if (color != null ) { setLabelTextColor(color); } //恢复标签文字大小 setLabelTextSize(bundle.getFloat(KEY_TEXT_SIZE_STATE, mTextSize)); // //恢复标签背景 (由于背景改用Drawable,所以不能自动保存和恢复) // int resId = bundle.getInt(KEY_BG_RES_ID_STATE, mLabelBgResId); // if (resId != 0) { // setLabelBackgroundResource(resId); // } //恢复标签内边距 int [] padding = bundle.getIntArray(KEY_PADDING_STATE); if (padding != null && padding.length == 4 ) { setLabelTextPadding(padding[ 0 ], padding[ 1 ], padding[ 2 ], padding[ 3 ]); } //恢复标签间隔 setWordMargin(bundle.getInt(KEY_WORD_MARGIN_STATE, mWordMargin)); //恢复行间隔 setLineMargin(bundle.getInt(KEY_LINE_MARGIN_STATE, mLineMargin)); //恢复标签的选择类型 setSelectType(SelectType.get(bundle.getInt(KEY_SELECT_TYPE_STATE, mSelectType.value))); //恢复标签的最大选择数量 setMaxSelect(bundle.getInt(KEY_MAX_SELECT_STATE, mMaxSelect)); //恢复标签的最少选择数量 setMinSelect(bundle.getInt(KEY_MIN_SELECT_STATE, mMinSelect)); //恢复标签的最大行数 setMaxLines(bundle.getInt(KEY_MAX_LINES_STATE, mMaxLines)); //恢复是否是指示器模式 setIndicator(bundle.getBoolean(KEY_INDICATOR_STATE, isIndicator)); // //恢复标签列表 // ArrayList<String> labels = bundle.getStringArrayList(KEY_LABELS_STATE); // if (labels != null && !labels.isEmpty()) { // setLabels(labels); // } //恢复必选项列表 ArrayList<Integer> compulsory = bundle.getIntegerArrayList(KEY_COMPULSORY_LABELS_STATE); if (compulsory != null && !compulsory.isEmpty()) { setCompulsorys(compulsory); } //恢复已选择的标签列表 ArrayList<Integer> selectLabel = bundle.getIntegerArrayList(KEY_SELECT_LABELS_STATE); if (selectLabel != null && !selectLabel.isEmpty()) { int size = selectLabel.size(); int [] positions = new int [size]; for ( int i = 0 ; i < size; i++) { positions[i] = selectLabel.get(i); } setSelects(positions); } return ; } super .onRestoreInstanceState(state); } /** * 设置标签列表 * * @param labels */ public void setLabels(List<String> labels) { setLabels(labels, new LabelTextProvider<String>() { @Override public CharSequence getLabelText(TextView label, int position, String data) { return data.trim(); } }); } /** * 设置标签列表,标签列表的数据可以是任何类型的数据,它最终显示的内容由LabelTextProvider根据标签的数据提供。 * * @param labels * @param provider * @param <T> */ public <T> void setLabels(List<T> labels, LabelTextProvider<T> provider) { //清空原有的标签 innerClearAllSelect(); removeAllViews(); mLabels.clear(); if (labels != null ) { mLabels.addAll(labels); int size = labels.size(); labelsTxt.clear(); for ( int i = 0 ; i < size; i++) { addLabel(labels.get(i), i, provider); } ensureLabelClickable(); } if (mSelectType == SelectType.SINGLE_IRREVOCABLY) { setSelects( 0 ); } } /** * 获取标签列表 * * @return */ public <T> List<T> getLabels() { return (List<T>) mLabels; } private <T> void addLabel(T data, int position, LabelTextProvider<T> provider) { final TextView label = new TextView(mContext); label.setPadding(mTextPaddingLeft, mTextPaddingTop, mTextPaddingRight, mTextPaddingBottom); label.setTextSize(TypedValue.COMPLEX_UNIT_PX, mTextSize); label.setTextColor(mTextColor != null ? mTextColor : ColorStateList.valueOf( 0xFF000000 )); //设置给label的背景(Drawable)是一个Drawable对象的拷贝, // 因为如果所有的标签都共用一个Drawable对象,会引起背景错乱。 label.setBackgroundDrawable(mLabelBg.getConstantState().newDrawable()); //label通过tag保存自己的数据(data)和位置(position) label.setTag(KEY_DATA, data); label.setTag(KEY_POSITION, position); label.setOnClickListener( this ); addView(label); labelsTxt.add(label); label.setText(provider.getLabelText(label, position, data)); } /** * 确保标签是否能响应事件,如果标签可选或者标签设置了点击事件监听,则响应事件。 */ private void ensureLabelClickable() { int count = getChildCount(); for ( int i = 0 ; i < count; i++) { TextView label = (TextView) getChildAt(i); label.setClickable(mLabelClickListener != null || mSelectType != SelectType.NONE); } } @Override public void onClick(View v) { if (v instanceof TextView) { TextView label = (TextView) v; if (!isIndicator) { if (mSelectType != SelectType.NONE) { if (label.isSelected()) { boolean irrevocable = mSelectType == SelectType.MULTI && mCompulsorys.contains((Integer) label.getTag(KEY_POSITION)); irrevocable = irrevocable || (mSelectType == SelectType.MULTI && mSelectLabels.size() <= mMinSelect); irrevocable = irrevocable || mSelectType == SelectType.SINGLE_IRREVOCABLY; if (!irrevocable) { setLabelSelect(label, false ); } } else { if (mSelectType == SelectType.SINGLE || mSelectType == SelectType.SINGLE_IRREVOCABLY) { innerClearAllSelect(); setLabelSelect(label, true ); } else if (mSelectType == SelectType.MULTI && (mMaxSelect <= 0 || mMaxSelect > mSelectLabels.size())) { setLabelSelect(label, true ); } } } } if (mLabelClickListener != null ) { mLabelClickListener.onLabelClick(label, label.getTag(KEY_DATA), ( int ) label.getTag(KEY_POSITION)); } } } private void setLabelSelect(TextView label, boolean isSelect) { if (label.isSelected() != isSelect) { label.setSelected(isSelect); if (isSelect) { mSelectLabels.add((Integer) label.getTag(KEY_POSITION)); } else { mSelectLabels.remove((Integer) label.getTag(KEY_POSITION)); } if (mLabelSelectChangeListener != null ) { mLabelSelectChangeListener.onLabelSelectChange(label, label.getTag(KEY_DATA), isSelect, ( int ) label.getTag(KEY_POSITION)); } } } /** * 取消所有选中的label */ public void clearAllSelect() { if (mSelectType != SelectType.SINGLE_IRREVOCABLY) { if (mSelectType == SelectType.MULTI && !mCompulsorys.isEmpty()) { clearNotCompulsorySelect(); } else { innerClearAllSelect(); } } } private void innerClearAllSelect() { int count = getChildCount(); for ( int i = 0 ; i < count; i++) { setLabelSelect((TextView) getChildAt(i), false ); } mSelectLabels.clear(); } private void clearNotCompulsorySelect() { int count = getChildCount(); List<Integer> temps = new ArrayList<>(); for ( int i = 0 ; i < count; i++) { if (!mCompulsorys.contains(i)) { setLabelSelect((TextView) getChildAt(i), false ); temps.add(i); } } mSelectLabels.removeAll(temps); } /** * 设置选中label * * @param positions */ public void setSelects(List<Integer> positions) { if (positions != null ) { int size = positions.size(); int [] ps = new int [size]; for ( int i = 0 ; i < size; i++) { ps[i] = positions.get(i); } setSelects(ps); } } /** * 设置选中label * 与原版不一样 去掉NONE判断 NONE可以选中 * * @param positions */ public void setSelects( int ... positions) { //if (mSelectType != SelectType.NONE) { ArrayList<TextView> selectLabels = new ArrayList<>(); int count = getChildCount(); int size = mSelectType == SelectType.SINGLE || mSelectType == SelectType.SINGLE_IRREVOCABLY ? 1 : mMaxSelect; for ( int p : positions) { if (p < count) { TextView label = (TextView) getChildAt(p); if (!selectLabels.contains(label)) { setLabelSelect(label, true ); selectLabels.add(label); } if (size > 0 && selectLabels.size() == size) { break ; } } } for ( int i = 0 ; i < count; i++) { TextView label = (TextView) getChildAt(i); if (!selectLabels.contains(label)) { setLabelSelect(label, false ); } } //} } /** * 设置必选项,只有在多项模式下,这个方法才有效 * * @param positions */ public void setCompulsorys(List<Integer> positions) { if (mSelectType == SelectType.MULTI && positions != null ) { mCompulsorys.clear(); mCompulsorys.addAll(positions); //必选项发生改变,就要恢复到初始状态。 innerClearAllSelect(); setSelects(positions); } } /** * 设置必选项,只有在多项模式下,这个方法才有效 * * @param positions */ public void setCompulsorys( int ... positions) { if (mSelectType == SelectType.MULTI && positions != null ) { List<Integer> ps = new ArrayList<>(positions.length); for ( int i : positions) { ps.add(i); } setCompulsorys(ps); } } /** * 获取必选项, * * @return */ public List<Integer> getCompulsorys() { return mCompulsorys; } /** * 清空必选项,只有在多项模式下,这个方法才有效 */ public void clearCompulsorys() { if (mSelectType == SelectType.MULTI && !mCompulsorys.isEmpty()) { mCompulsorys.clear(); //必选项发生改变,就要恢复到初始状态。 innerClearAllSelect(); } } /** * 获取选中的label(返回的是所有选中的标签的位置) * * @return */ public List<Integer> getSelectLabels() { return mSelectLabels; } /** * 获取选中的label(返回的是所头选中的标签的数据) * * @param <T> * @return */ public <T> List<T> getSelectLabelDatas() { List<T> list = new ArrayList<>(); int size = mSelectLabels.size(); for ( int i = 0 ; i < size; i++) { View label = getChildAt(mSelectLabels.get(i)); Object data = label.getTag(KEY_DATA); if (data != null ) { list.add((T) data); } } return list; } /** * 设置标签背景 * * @param resId */ public void setLabelBackgroundResource( int resId) { setLabelBackgroundDrawable(getResources().getDrawable(resId)); } /** * 设置标签背景 * * @param color */ public void setLabelBackgroundColor( int color) { setLabelBackgroundDrawable( new ColorDrawable(color)); } /** * 设置标签背景 * * @param drawable */ public void setLabelBackgroundDrawable(Drawable drawable) { mLabelBg = drawable; int count = getChildCount(); for ( int i = 0 ; i < count; i++) { TextView label = (TextView) getChildAt(i); label.setBackgroundDrawable(mLabelBg.getConstantState().newDrawable()); } } /** * 设置标签内边距 * * @param left * @param top * @param right * @param bottom */ public void setLabelTextPadding( int left, int top, int right, int bottom) { if (mTextPaddingLeft != left || mTextPaddingTop != top || mTextPaddingRight != right || mTextPaddingBottom != bottom) { mTextPaddingLeft = left; mTextPaddingTop = top; mTextPaddingRight = right; mTextPaddingBottom = bottom; int count = getChildCount(); for ( int i = 0 ; i < count; i++) { TextView label = (TextView) getChildAt(i); label.setPadding(left, top, right, bottom); } } } public int getTextPaddingLeft() { return mTextPaddingLeft; } public int getTextPaddingTop() { return mTextPaddingTop; } public int getTextPaddingRight() { return mTextPaddingRight; } public int getTextPaddingBottom() { return mTextPaddingBottom; } /** * 设置标签的文字大小(单位是px) * * @param size */ public void setLabelTextSize( float size) { if (mTextSize != size) { mTextSize = size; int count = getChildCount(); for ( int i = 0 ; i < count; i++) { TextView label = (TextView) getChildAt(i); label.setTextSize(TypedValue.COMPLEX_UNIT_PX, size); } } } public float getLabelTextSize() { return mTextSize; } /** * 设置标签的文字颜色 * * @param color */ public void setLabelTextColor( int color) { setLabelTextColor(ColorStateList.valueOf(color)); } /** * 设置标签的文字颜色 * * @param color */ public void setLabelTextColor(ColorStateList color) { mTextColor = color; int count = getChildCount(); for ( int i = 0 ; i < count; i++) { TextView label = (TextView) getChildAt(i); label.setTextColor(mTextColor != null ? mTextColor : ColorStateList.valueOf( 0xFF000000 )); } } public ColorStateList getLabelTextColor() { return mTextColor; } /** * 设置行间隔 */ public void setLineMargin( int margin) { if (mLineMargin != margin) { mLineMargin = margin; requestLayout(); } } public int getLineMargin() { return mLineMargin; } /** * 设置标签的间隔 */ public void setWordMargin( int margin) { if (mWordMargin != margin) { mWordMargin = margin; requestLayout(); } } public int getWordMargin() { return mWordMargin; } /** * 设置标签的选择类型 * * @param selectType */ public void setSelectType(SelectType selectType) { if (mSelectType != selectType) { mSelectType = selectType; //选择类型发生改变,就要恢复到初始状态。 innerClearAllSelect(); if (mSelectType == SelectType.SINGLE_IRREVOCABLY) { setSelects( 0 ); } if (mSelectType != SelectType.MULTI) { mCompulsorys.clear(); } ensureLabelClickable(); } } public SelectType getSelectType() { return mSelectType; } /** * 设置最大的选择数量,只有selectType等于MULTI时有效。 * * @param maxSelect */ public void setMaxSelect( int maxSelect) { if (mMaxSelect != maxSelect) { mMaxSelect = maxSelect; if (mSelectType == SelectType.MULTI) { //最大选择数量发生改变,就要恢复到初始状态。 innerClearAllSelect(); } } } public int getMaxSelect() { return mMaxSelect; } /** * 设置最少的选择数量,只有selectType等于MULTI时有效。 * 注意:minSelect只限制用户手动点击取消选中时的效果。 * 调用setSelects()、clearAllSelect()等方法改变标签的选中状态时,不受minSelect影响。 * * @param minSelect */ public void setMinSelect( int minSelect) { this .mMinSelect = minSelect; } public int getMinSelect() { return mMinSelect; } /** * 设置最大行数,小于等于0则不限行数。 * * @param maxLines */ public void setMaxLines( int maxLines) { if (mMaxLines != maxLines) { mMaxLines = maxLines; requestLayout(); } } public int getMaxLines() { return mMaxLines; } /** * 设置为指示器模式,只能看,不能手动操作。这种模式下,用户不能通过手动点击改变标签的选中状态。 * 但是仍然可以通过调用setSelects()、clearAllSelect()等方法改变标签的选中状态。 * * @param indicator */ public void setIndicator( boolean indicator) { isIndicator = indicator; } public boolean isIndicator() { return isIndicator; } /** * 设置能否选中 * * @param index */ public void setLabelsCantClick( int index, boolean enable) { if (labelsTxt.size() > index) { labelsTxt.get(index).setEnabled(enable); } } /** * 设置所有均可选中 */ public void setLabelsAllClick() { for (TextView textView : labelsTxt) { textView.setEnabled( true ); } } /** * 设置标签的点击监听 * * @param l */ public void setOnLabelClickListener(OnLabelClickListener l) { mLabelClickListener = l; ensureLabelClickable(); } /** * 设置标签的选择监听 * * @param l */ public void setOnLabelSelectChangeListener(OnLabelSelectChangeListener l) { mLabelSelectChangeListener = l; } /** * sp转px */ public static int sp2px(Context context, float spVal) { return ( int ) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, spVal, context.getResources().getDisplayMetrics()); } public interface OnLabelClickListener { /** * @param label 标签 * @param data 标签对应的数据 * @param position 标签位置 */ void onLabelClick(TextView label, Object data, int position); } public interface OnLabelSelectChangeListener { /** * @param label 标签 * @param data 标签对应的数据 * @param isSelect 是否选中 * @param position 标签位置 */ void onLabelSelectChange(TextView label, Object data, boolean isSelect, int position); } /** * 给标签提供最终需要显示的数据。因为LabelsView的列表可以设置任何类型的数据,而LabelsView里的每个item的是一 * 个TextView,只能显示CharSequence的数据,所以LabelTextProvider需要根据每个item的数据返回item最终要显示 * 的CharSequence。 * * @param <T> */ public interface LabelTextProvider<T> { /** * 根据data和position返回label需要需要显示的数据。 * * @param label * @param position * @param data * @return */ CharSequence getLabelText(TextView label, int position, T data); } } |
标签用法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | <TabLabelsView android:id= "@+id/labels" android:layout_width= "match_parent" android:layout_height= "wrap_content" android:background= "@color/white" android:padding= "@dimen/dp_10" app:labelBackground= "@drawable/labels_bg" app:labelTextColor= "@drawable/labels_color" app:labelTextPaddingBottom= "5dp" app:labelTextPaddingLeft= "10dp" app:labelTextPaddingRight= "10dp" app:labelTextPaddingTop= "5dp" app:labelTextSize= "14sp" app:lineMargin= "8dp" app:minSelect= "1" app:wordMargin= "5dp" /> |
自定义属性attrs.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | <declare-styleable name= "tab_labels_view" > <attr name= "selectType" format= "enum" > < enum name= "NONE" value= "1" /> < enum name= "SINGLE" value= "2" /> < enum name= "SINGLE_IRREVOCABLY" value= "3" /> < enum name= "MULTI" value= "4" /> </attr> <attr name= "maxSelect" format= "integer" /> <attr name= "minSelect" format= "integer" /> <attr name= "maxLines" format= "integer" /> <attr name= "isIndicator" format= "boolean" /> <attr name= "labelTextColor" format= "reference|color" /> <attr name= "labelTextSize" format= "dimension" /> <attr name= "labelTextPaddingLeft" format= "dimension" /> <attr name= "labelTextPaddingTop" format= "dimension" /> <attr name= "labelTextPaddingRight" format= "dimension" /> <attr name= "labelTextPaddingBottom" format= "dimension" /> <attr name= "lineMargin" format= "dimension" /> <attr name= "wordMargin" format= "dimension" /> <attr name= "labelBackground" format= "reference|color" /> </declare-styleable> <item name= "tag_key_data" type= "id" /> <item name= "tag_key_position" type= "id" /> |
添加数据
1 2 3 4 5 6 7 8 | TabLabelsView labels; ArrayList<String> items = new ArrayList<>(); for ( int i = 0 ; i < 10 ; i++) { items.add( "数据item" + i); } labels.setLabels(items); labels.setSelectType(TabLabelsView.SelectType.MULTI); labels.setLabelsCantClick( 5 , false ); |
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek “源神”启动!「GitHub 热点速览」
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 我与微信审核的“相爱相杀”看个人小程序副业
· C# 集成 DeepSeek 模型实现 AI 私有化(本地部署与 API 调用教程)
· spring官宣接入deepseek,真的太香了~
2021-05-27 MMKV使用笔记