├── .classpath ├── .gitignore ├── .idea ├── .name ├── compiler.xml ├── copyright │ └── profiles_settings.xml ├── encodings.xml ├── libraries │ └── commons_lang3_3_3_2.xml ├── misc.xml ├── modules.xml ├── scopes │ └── scope_settings.xml ├── uiDesigner.xml ├── vcs.xml └── workspace.xml ├── .project ├── .settings ├── org.eclipse.core.resources.prefs └── org.eclipse.jdt.core.prefs ├── AndroidManifest.xml ├── PinnedHeaderListViewDemo.iml ├── README.md ├── assets └── city.db ├── bin ├── AndroidManifest.xml └── classes │ └── com │ └── example │ └── pinnedheaderlistviewdemo │ ├── BuildConfig.class │ ├── City.class │ ├── MainActivity$1.class │ ├── MainActivity$2.class │ ├── MainActivity$3.class │ ├── MainActivity$4.class │ ├── MainActivity$5.class │ ├── MainActivity$MyComparator.class │ ├── MainActivity.class │ ├── MySectionIndexer.class │ ├── R$attr.class │ ├── R$color.class │ ├── R$dimen.class │ ├── R$drawable.class │ ├── R$id.class │ ├── R$layout.class │ ├── R$menu.class │ ├── R$string.class │ ├── R$style.class │ ├── R.class │ ├── adapter │ ├── CityListAdapter$ViewHolder.class │ └── CityListAdapter.class │ ├── db │ ├── CityDao.class │ ├── DBHelper$TableData$CityTable.class │ ├── DBHelper$TableData.class │ └── DBHelper.class │ └── view │ ├── BladeView$1.class │ ├── BladeView$OnItemClickListener.class │ ├── BladeView.class │ ├── PinnedHeaderListView$PinnedHeaderAdapter.class │ └── PinnedHeaderListView.class ├── ic_launcher-web.png ├── img ├── img_common.png └── img_dynamic.png ├── libs ├── android-support-v4.jar └── commons-lang3-3.3.2.jar ├── proguard-project.txt ├── project.properties ├── res ├── drawable-hdpi │ └── ic_launcher.png ├── drawable-mdpi │ └── ic_launcher.png ├── drawable-xhdpi │ └── ic_launcher.png ├── drawable-xxhdpi │ └── ic_launcher.png ├── layout-large │ └── activity_main.xml ├── layout │ ├── activity_main.xml │ ├── list_group_item.xml │ ├── main_twopanes.xml │ └── select_city_item.xml ├── menu │ └── main.xml ├── values-large │ └── layout.xml ├── values-sw600dp │ └── dimens.xml ├── values-sw720dp-land │ └── dimens.xml └── values │ ├── colors.xml │ ├── dimens.xml │ ├── strings.xml │ └── styles.xml └── src └── com └── example └── pinnedheaderlistviewdemo ├── City.java ├── MainActivity.java ├── MySectionIndexer.java ├── adapter └── CityListAdapter.java ├── db ├── CityDao.java └── DBHelper.java └── view ├── BladeView.java └── PinnedHeaderListView.java /.classpath: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | gen 2 | *.apk 3 | *.zip 4 | 5 | -------------------------------------------------------------------------------- /.idea/.name: -------------------------------------------------------------------------------- 1 | PinnedHeaderListViewDemo -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /.idea/copyright/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/libraries/commons_lang3_3_3_2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /.idea/scopes/scope_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /.idea/uiDesigner.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 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 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/workspace.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 9 | 10 | 11 | 12 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 5.4in FWVGA 24 | @style/AppTheme 25 | 26 | 27 | 28 | 29 | 30 | 32 | 33 | 34 | 35 | 36 | 42 | 43 | 44 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 68 | 69 | 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 | 155 | 156 | 157 | 173 | 174 | 175 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 213 | 214 | 215 | 216 | 219 | 220 | 223 | 224 | 225 | 226 | 229 | 230 | 233 | 234 | 237 | 238 | 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 | 285 | 286 | 287 | 305 | 306 | 313 | 314 | 315 | 333 | 340 | 341 | 342 | 353 | 354 | 367 | 368 | 369 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 406 | 407 | 408 | 427 | 428 | 429 | 430 | 431 | localhost 432 | 5050 433 | 434 | 435 | 436 | 437 | 440 | 441 | 442 | 459 | 460 | 461 | 462 | 1395714125379 463 | 1395714125379 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 | 504 | 505 | 506 | 508 | 509 | 512 | 513 | 514 | 515 | 516 | 517 | 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 | 902 | 903 | 904 | 905 | 906 | 907 | Android 908 | 909 | 914 | 915 | 916 | 917 | 918 | 919 | android-support-v4 920 | 921 | 926 | 927 | 928 | 929 | 930 | 931 | 1.7 932 | 933 | 938 | 939 | 940 | 941 | 942 | 943 | PinnedHeaderListViewDemo 944 | 945 | 950 | 951 | 952 | 953 | 954 | 955 | Android API 17 Platform 956 | 957 | 962 | 963 | 964 | 965 | 966 | 967 | commons-lang3-3.3.2 968 | 969 | 974 | 975 | 976 | 977 | 978 | 979 | 980 | 981 | 984 | 985 | 1013 | 1014 | 1020 | 1021 | 1028 | 1029 | 1030 | 1031 | -------------------------------------------------------------------------------- /.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | PinnedHeaderListViewDemo 4 | 5 | 6 | 7 | 8 | 9 | com.android.ide.eclipse.adt.ResourceManagerBuilder 10 | 11 | 12 | 13 | 14 | com.android.ide.eclipse.adt.PreCompilerBuilder 15 | 16 | 17 | 18 | 19 | org.eclipse.jdt.core.javabuilder 20 | 21 | 22 | 23 | 24 | com.android.ide.eclipse.adt.ApkBuilder 25 | 26 | 27 | 28 | 29 | 30 | com.android.ide.eclipse.adt.AndroidNature 31 | org.eclipse.jdt.core.javanature 32 | 33 | 34 | -------------------------------------------------------------------------------- /.settings/org.eclipse.core.resources.prefs: -------------------------------------------------------------------------------- 1 | eclipse.preferences.version=1 2 | encoding/=UTF-8 3 | -------------------------------------------------------------------------------- /.settings/org.eclipse.jdt.core.prefs: -------------------------------------------------------------------------------- 1 | eclipse.preferences.version=1 2 | org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6 3 | org.eclipse.jdt.core.compiler.compliance=1.6 4 | org.eclipse.jdt.core.compiler.source=1.6 5 | -------------------------------------------------------------------------------- /AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 10 | 11 | 12 | 17 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /PinnedHeaderListViewDemo.iml: -------------------------------------------------------------------------------- 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | PinnedHeaderListViewDemo 2 | ======================== 3 | 4 | #### 5 |
6 | 正在更新的版本: 7 | 8 | 实现右边竖向选择器中的字母动态生成(由数据库中的数据) 9 |
10 | ###PinnedHeaderListViewDemo仿联系人(实际为城市选择) 11 | ##如图所示: 12 | ####**1、普通状态** 13 | ![普通状态](./img/img_common.png) 14 | ####**2、滑动状态** 15 | ![普通状态](./img/img_dynamic.png) 16 | 17 | 18 | -------------------------------------------------------------------------------- /assets/city.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/itlonewolf/PinnedHeaderListViewDemo/8ce47efcb3fb09c848f04eaa763419aadc2da7f8/assets/city.db -------------------------------------------------------------------------------- /bin/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 10 | 11 | 12 | 17 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /bin/classes/com/example/pinnedheaderlistviewdemo/BuildConfig.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/itlonewolf/PinnedHeaderListViewDemo/8ce47efcb3fb09c848f04eaa763419aadc2da7f8/bin/classes/com/example/pinnedheaderlistviewdemo/BuildConfig.class -------------------------------------------------------------------------------- /bin/classes/com/example/pinnedheaderlistviewdemo/City.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/itlonewolf/PinnedHeaderListViewDemo/8ce47efcb3fb09c848f04eaa763419aadc2da7f8/bin/classes/com/example/pinnedheaderlistviewdemo/City.class -------------------------------------------------------------------------------- /bin/classes/com/example/pinnedheaderlistviewdemo/MainActivity$1.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/itlonewolf/PinnedHeaderListViewDemo/8ce47efcb3fb09c848f04eaa763419aadc2da7f8/bin/classes/com/example/pinnedheaderlistviewdemo/MainActivity$1.class -------------------------------------------------------------------------------- /bin/classes/com/example/pinnedheaderlistviewdemo/MainActivity$2.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/itlonewolf/PinnedHeaderListViewDemo/8ce47efcb3fb09c848f04eaa763419aadc2da7f8/bin/classes/com/example/pinnedheaderlistviewdemo/MainActivity$2.class -------------------------------------------------------------------------------- /bin/classes/com/example/pinnedheaderlistviewdemo/MainActivity$3.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/itlonewolf/PinnedHeaderListViewDemo/8ce47efcb3fb09c848f04eaa763419aadc2da7f8/bin/classes/com/example/pinnedheaderlistviewdemo/MainActivity$3.class -------------------------------------------------------------------------------- /bin/classes/com/example/pinnedheaderlistviewdemo/MainActivity$4.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/itlonewolf/PinnedHeaderListViewDemo/8ce47efcb3fb09c848f04eaa763419aadc2da7f8/bin/classes/com/example/pinnedheaderlistviewdemo/MainActivity$4.class -------------------------------------------------------------------------------- /bin/classes/com/example/pinnedheaderlistviewdemo/MainActivity$5.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/itlonewolf/PinnedHeaderListViewDemo/8ce47efcb3fb09c848f04eaa763419aadc2da7f8/bin/classes/com/example/pinnedheaderlistviewdemo/MainActivity$5.class -------------------------------------------------------------------------------- /bin/classes/com/example/pinnedheaderlistviewdemo/MainActivity$MyComparator.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/itlonewolf/PinnedHeaderListViewDemo/8ce47efcb3fb09c848f04eaa763419aadc2da7f8/bin/classes/com/example/pinnedheaderlistviewdemo/MainActivity$MyComparator.class -------------------------------------------------------------------------------- /bin/classes/com/example/pinnedheaderlistviewdemo/MainActivity.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/itlonewolf/PinnedHeaderListViewDemo/8ce47efcb3fb09c848f04eaa763419aadc2da7f8/bin/classes/com/example/pinnedheaderlistviewdemo/MainActivity.class -------------------------------------------------------------------------------- /bin/classes/com/example/pinnedheaderlistviewdemo/MySectionIndexer.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/itlonewolf/PinnedHeaderListViewDemo/8ce47efcb3fb09c848f04eaa763419aadc2da7f8/bin/classes/com/example/pinnedheaderlistviewdemo/MySectionIndexer.class -------------------------------------------------------------------------------- /bin/classes/com/example/pinnedheaderlistviewdemo/R$attr.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/itlonewolf/PinnedHeaderListViewDemo/8ce47efcb3fb09c848f04eaa763419aadc2da7f8/bin/classes/com/example/pinnedheaderlistviewdemo/R$attr.class -------------------------------------------------------------------------------- /bin/classes/com/example/pinnedheaderlistviewdemo/R$color.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/itlonewolf/PinnedHeaderListViewDemo/8ce47efcb3fb09c848f04eaa763419aadc2da7f8/bin/classes/com/example/pinnedheaderlistviewdemo/R$color.class -------------------------------------------------------------------------------- /bin/classes/com/example/pinnedheaderlistviewdemo/R$dimen.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/itlonewolf/PinnedHeaderListViewDemo/8ce47efcb3fb09c848f04eaa763419aadc2da7f8/bin/classes/com/example/pinnedheaderlistviewdemo/R$dimen.class -------------------------------------------------------------------------------- /bin/classes/com/example/pinnedheaderlistviewdemo/R$drawable.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/itlonewolf/PinnedHeaderListViewDemo/8ce47efcb3fb09c848f04eaa763419aadc2da7f8/bin/classes/com/example/pinnedheaderlistviewdemo/R$drawable.class -------------------------------------------------------------------------------- /bin/classes/com/example/pinnedheaderlistviewdemo/R$id.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/itlonewolf/PinnedHeaderListViewDemo/8ce47efcb3fb09c848f04eaa763419aadc2da7f8/bin/classes/com/example/pinnedheaderlistviewdemo/R$id.class -------------------------------------------------------------------------------- /bin/classes/com/example/pinnedheaderlistviewdemo/R$layout.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/itlonewolf/PinnedHeaderListViewDemo/8ce47efcb3fb09c848f04eaa763419aadc2da7f8/bin/classes/com/example/pinnedheaderlistviewdemo/R$layout.class -------------------------------------------------------------------------------- /bin/classes/com/example/pinnedheaderlistviewdemo/R$menu.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/itlonewolf/PinnedHeaderListViewDemo/8ce47efcb3fb09c848f04eaa763419aadc2da7f8/bin/classes/com/example/pinnedheaderlistviewdemo/R$menu.class -------------------------------------------------------------------------------- /bin/classes/com/example/pinnedheaderlistviewdemo/R$string.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/itlonewolf/PinnedHeaderListViewDemo/8ce47efcb3fb09c848f04eaa763419aadc2da7f8/bin/classes/com/example/pinnedheaderlistviewdemo/R$string.class -------------------------------------------------------------------------------- /bin/classes/com/example/pinnedheaderlistviewdemo/R$style.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/itlonewolf/PinnedHeaderListViewDemo/8ce47efcb3fb09c848f04eaa763419aadc2da7f8/bin/classes/com/example/pinnedheaderlistviewdemo/R$style.class -------------------------------------------------------------------------------- /bin/classes/com/example/pinnedheaderlistviewdemo/R.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/itlonewolf/PinnedHeaderListViewDemo/8ce47efcb3fb09c848f04eaa763419aadc2da7f8/bin/classes/com/example/pinnedheaderlistviewdemo/R.class -------------------------------------------------------------------------------- /bin/classes/com/example/pinnedheaderlistviewdemo/adapter/CityListAdapter$ViewHolder.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/itlonewolf/PinnedHeaderListViewDemo/8ce47efcb3fb09c848f04eaa763419aadc2da7f8/bin/classes/com/example/pinnedheaderlistviewdemo/adapter/CityListAdapter$ViewHolder.class -------------------------------------------------------------------------------- /bin/classes/com/example/pinnedheaderlistviewdemo/adapter/CityListAdapter.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/itlonewolf/PinnedHeaderListViewDemo/8ce47efcb3fb09c848f04eaa763419aadc2da7f8/bin/classes/com/example/pinnedheaderlistviewdemo/adapter/CityListAdapter.class -------------------------------------------------------------------------------- /bin/classes/com/example/pinnedheaderlistviewdemo/db/CityDao.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/itlonewolf/PinnedHeaderListViewDemo/8ce47efcb3fb09c848f04eaa763419aadc2da7f8/bin/classes/com/example/pinnedheaderlistviewdemo/db/CityDao.class -------------------------------------------------------------------------------- /bin/classes/com/example/pinnedheaderlistviewdemo/db/DBHelper$TableData$CityTable.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/itlonewolf/PinnedHeaderListViewDemo/8ce47efcb3fb09c848f04eaa763419aadc2da7f8/bin/classes/com/example/pinnedheaderlistviewdemo/db/DBHelper$TableData$CityTable.class -------------------------------------------------------------------------------- /bin/classes/com/example/pinnedheaderlistviewdemo/db/DBHelper$TableData.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/itlonewolf/PinnedHeaderListViewDemo/8ce47efcb3fb09c848f04eaa763419aadc2da7f8/bin/classes/com/example/pinnedheaderlistviewdemo/db/DBHelper$TableData.class -------------------------------------------------------------------------------- /bin/classes/com/example/pinnedheaderlistviewdemo/db/DBHelper.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/itlonewolf/PinnedHeaderListViewDemo/8ce47efcb3fb09c848f04eaa763419aadc2da7f8/bin/classes/com/example/pinnedheaderlistviewdemo/db/DBHelper.class -------------------------------------------------------------------------------- /bin/classes/com/example/pinnedheaderlistviewdemo/view/BladeView$1.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/itlonewolf/PinnedHeaderListViewDemo/8ce47efcb3fb09c848f04eaa763419aadc2da7f8/bin/classes/com/example/pinnedheaderlistviewdemo/view/BladeView$1.class -------------------------------------------------------------------------------- /bin/classes/com/example/pinnedheaderlistviewdemo/view/BladeView$OnItemClickListener.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/itlonewolf/PinnedHeaderListViewDemo/8ce47efcb3fb09c848f04eaa763419aadc2da7f8/bin/classes/com/example/pinnedheaderlistviewdemo/view/BladeView$OnItemClickListener.class -------------------------------------------------------------------------------- /bin/classes/com/example/pinnedheaderlistviewdemo/view/BladeView.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/itlonewolf/PinnedHeaderListViewDemo/8ce47efcb3fb09c848f04eaa763419aadc2da7f8/bin/classes/com/example/pinnedheaderlistviewdemo/view/BladeView.class -------------------------------------------------------------------------------- /bin/classes/com/example/pinnedheaderlistviewdemo/view/PinnedHeaderListView$PinnedHeaderAdapter.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/itlonewolf/PinnedHeaderListViewDemo/8ce47efcb3fb09c848f04eaa763419aadc2da7f8/bin/classes/com/example/pinnedheaderlistviewdemo/view/PinnedHeaderListView$PinnedHeaderAdapter.class -------------------------------------------------------------------------------- /bin/classes/com/example/pinnedheaderlistviewdemo/view/PinnedHeaderListView.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/itlonewolf/PinnedHeaderListViewDemo/8ce47efcb3fb09c848f04eaa763419aadc2da7f8/bin/classes/com/example/pinnedheaderlistviewdemo/view/PinnedHeaderListView.class -------------------------------------------------------------------------------- /ic_launcher-web.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/itlonewolf/PinnedHeaderListViewDemo/8ce47efcb3fb09c848f04eaa763419aadc2da7f8/ic_launcher-web.png -------------------------------------------------------------------------------- /img/img_common.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/itlonewolf/PinnedHeaderListViewDemo/8ce47efcb3fb09c848f04eaa763419aadc2da7f8/img/img_common.png -------------------------------------------------------------------------------- /img/img_dynamic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/itlonewolf/PinnedHeaderListViewDemo/8ce47efcb3fb09c848f04eaa763419aadc2da7f8/img/img_dynamic.png -------------------------------------------------------------------------------- /libs/android-support-v4.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/itlonewolf/PinnedHeaderListViewDemo/8ce47efcb3fb09c848f04eaa763419aadc2da7f8/libs/android-support-v4.jar -------------------------------------------------------------------------------- /libs/commons-lang3-3.3.2.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/itlonewolf/PinnedHeaderListViewDemo/8ce47efcb3fb09c848f04eaa763419aadc2da7f8/libs/commons-lang3-3.3.2.jar -------------------------------------------------------------------------------- /proguard-project.txt: -------------------------------------------------------------------------------- 1 | # To enable ProGuard in your project, edit project.properties 2 | # to define the proguard.config property as described in that file. 3 | # 4 | # Add project specific ProGuard rules here. 5 | # By default, the flags in this file are appended to flags specified 6 | # in ${sdk.dir}/tools/proguard/proguard-android.txt 7 | # You can edit the include path and order by changing the ProGuard 8 | # include property in project.properties. 9 | # 10 | # For more details, see 11 | # http://developer.android.com/guide/developing/tools/proguard.html 12 | 13 | # Add any project specific keep options here: 14 | 15 | # If your project uses WebView with JS, uncomment the following 16 | # and specify the fully qualified class name to the JavaScript interface 17 | # class: 18 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 19 | # public *; 20 | #} 21 | -------------------------------------------------------------------------------- /project.properties: -------------------------------------------------------------------------------- 1 | # This file is automatically generated by Android Tools. 2 | # Do not modify this file -- YOUR CHANGES WILL BE ERASED! 3 | # 4 | # This file must be checked in Version Control Systems. 5 | # 6 | # To customize properties used by the Ant build system edit 7 | # "ant.properties", and override values to adapt the script to your 8 | # project structure. 9 | # 10 | # To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home): 11 | #proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt 12 | 13 | # Project target. 14 | target=android-8 15 | -------------------------------------------------------------------------------- /res/drawable-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/itlonewolf/PinnedHeaderListViewDemo/8ce47efcb3fb09c848f04eaa763419aadc2da7f8/res/drawable-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /res/drawable-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/itlonewolf/PinnedHeaderListViewDemo/8ce47efcb3fb09c848f04eaa763419aadc2da7f8/res/drawable-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /res/drawable-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/itlonewolf/PinnedHeaderListViewDemo/8ce47efcb3fb09c848f04eaa763419aadc2da7f8/res/drawable-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /res/drawable-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/itlonewolf/PinnedHeaderListViewDemo/8ce47efcb3fb09c848f04eaa763419aadc2da7f8/res/drawable-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /res/layout-large/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | -------------------------------------------------------------------------------- /res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 14 | 15 | 21 | 22 | -------------------------------------------------------------------------------- /res/layout/list_group_item.xml: -------------------------------------------------------------------------------- 1 | 2 | 14 | -------------------------------------------------------------------------------- /res/layout/main_twopanes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | -------------------------------------------------------------------------------- /res/layout/select_city_item.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 20 | 21 | 31 | 32 | -------------------------------------------------------------------------------- /res/menu/main.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | -------------------------------------------------------------------------------- /res/values-large/layout.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | @layout/main_twopanes 4 | -------------------------------------------------------------------------------- /res/values-sw600dp/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | -------------------------------------------------------------------------------- /res/values-sw720dp-land/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 128dp 8 | 9 | -------------------------------------------------------------------------------- /res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #000000 4 | #FFFFFF 5 | #CACACA 6 | #8AE220 7 | 8 | -------------------------------------------------------------------------------- /res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 16dp 5 | 16dp 6 | 14dp 7 | 80dp 8 | 9 | 12sp 10 | 20sp 11 | 12 | -------------------------------------------------------------------------------- /res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | PinnedHeaderListViewDemo 5 | Settings 6 | Hello world! 7 | 8 | -------------------------------------------------------------------------------- /res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 14 | 15 | 16 | 19 | 20 | -------------------------------------------------------------------------------- /src/com/example/pinnedheaderlistviewdemo/City.java: -------------------------------------------------------------------------------- 1 | package com.example.pinnedheaderlistviewdemo; 2 | 3 | public class City { 4 | private String id; 5 | private String name; 6 | 7 | private String pyf;/*城市拼音全拼 spell full*/ 8 | private String pys;/*城市拼音缩写 spell short*/ 9 | 10 | public String getId() { 11 | return id; 12 | } 13 | 14 | public void setId(String id) { 15 | this.id = id; 16 | } 17 | 18 | public String getName() { 19 | return name; 20 | } 21 | 22 | public void setName(String name) { 23 | this.name = name; 24 | } 25 | 26 | public String getPyf() { 27 | return pyf; 28 | } 29 | 30 | public void setPyf(String pyf) { 31 | this.pyf = pyf; 32 | } 33 | 34 | public String getPys() { 35 | return pys; 36 | } 37 | 38 | public void setPys(String pys) { 39 | this.pys = pys; 40 | } 41 | 42 | /** 43 | * 获取拼音的简写 第一个字母 44 | * abbreviation 缩写词 45 | * 名字应该改为 getFirstWordOfAbbreviation 46 | * @return 47 | */ 48 | public String getSortKey() { 49 | return pys.substring(0, 1); 50 | } 51 | 52 | @Override 53 | public String toString() { 54 | return "City{" + 55 | "id='" + id + '\'' + 56 | ", name='" + name + '\'' + 57 | ", 拼音='" + pyf + '\'' + 58 | ", 缩写='" + pys + '\'' + 59 | '}'; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/com/example/pinnedheaderlistviewdemo/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.example.pinnedheaderlistviewdemo; 2 | 3 | import android.app.Activity; 4 | import android.os.Bundle; 5 | import android.os.Environment; 6 | import android.os.Handler; 7 | import android.util.Log; 8 | import android.view.LayoutInflater; 9 | import android.view.View; 10 | import android.widget.AdapterView; 11 | import android.widget.Toast; 12 | import com.example.pinnedheaderlistviewdemo.adapter.CityListAdapter; 13 | import com.example.pinnedheaderlistviewdemo.db.CityDao; 14 | import com.example.pinnedheaderlistviewdemo.db.DBHelper; 15 | import com.example.pinnedheaderlistviewdemo.view.BladeView; 16 | import com.example.pinnedheaderlistviewdemo.view.BladeView.OnItemClickListener; 17 | import com.example.pinnedheaderlistviewdemo.view.PinnedHeaderListView; 18 | 19 | import java.io.File; 20 | import java.io.FileOutputStream; 21 | import java.io.InputStream; 22 | import java.util.*; 23 | 24 | public class MainActivity extends Activity { 25 | 26 | private static final int COPY_DB_SUCCESS = 10; 27 | private static final int COPY_DB_FAILED = 11; 28 | protected static final int QUERY_CITY_FINISH = 12; 29 | private MySectionIndexer mIndexer; 30 | 31 | private List mCityList = new ArrayList(); 32 | 33 | 34 | private DBHelper mHelper; 35 | private CityDao mDao; 36 | 37 | private CityListAdapter mAdapter; 38 | public static String ALL_CHARACTER ; 39 | // public static String ALL_CHARACTER = "#ABCDFGHJKLMNOPQRSTWXYZ"; 40 | protected static final String TAG = null; 41 | 42 | private static final String TAG_= "MainActivity" ; 43 | 44 | private String[] sections ; 45 | // private String[] sections = {"当前", "A", "B", "C", "D", "F", "G", "H", "J", "K", 46 | // "L", "M", "N", "O", "P", "Q", "R", "S", "T","W", "X", 47 | // "Y", "Z"}; 48 | private int[] counts; 49 | private PinnedHeaderListView mListView; 50 | 51 | 52 | public static String APP_DIR = Environment.getExternalStorageDirectory().getAbsolutePath() + "/test/"; 53 | private Handler handler = new Handler() { 54 | 55 | public void handleMessage(android.os.Message msg) { 56 | switch (msg.what) { 57 | case QUERY_CITY_FINISH: 58 | 59 | if (mAdapter == null) { 60 | 61 | mIndexer = new MySectionIndexer(sections, counts); 62 | 63 | mAdapter = new CityListAdapter(mCityList, mIndexer, getApplicationContext()); 64 | mListView.setAdapter(mAdapter); 65 | 66 | mListView.setOnScrollListener(mAdapter); 67 | 68 | //設置頂部固定頭部 69 | mListView.setPinnedHeaderView(LayoutInflater.from(getApplicationContext()).inflate( 70 | R.layout.list_group_item, mListView, false)); 71 | 72 | } else if (mAdapter != null) { 73 | mAdapter.notifyDataSetChanged(); 74 | } 75 | 76 | break; 77 | 78 | case COPY_DB_SUCCESS: 79 | requestData(); 80 | break; 81 | default: 82 | break; 83 | } 84 | } 85 | 86 | ; 87 | }; 88 | 89 | 90 | @Override 91 | protected void onCreate(Bundle savedInstanceState) { 92 | super.onCreate(savedInstanceState); 93 | setContentView(R.layout.activity_main); 94 | 95 | Log.d(TAG_,"MainActivity onCreate") ; 96 | mHelper = new DBHelper(); 97 | mDao = new CityDao(mHelper); 98 | init(); 99 | copyDBFile(); 100 | findView(); 101 | } 102 | 103 | private void init(){ 104 | sections = mDao.sectionsAndBlade("当前") ; 105 | ALL_CHARACTER = mDao.allCharacter() ; 106 | } 107 | 108 | 109 | private void copyDBFile() { 110 | 111 | File file = new File(APP_DIR + "/city.db"); 112 | if (file.exists()) { 113 | requestData(); 114 | Log.w(TAG_,"") ; 115 | 116 | } else { //拷贝文件 117 | Runnable task = new Runnable() { 118 | 119 | @Override 120 | public void run() { 121 | 122 | copyAssetsFile2SDCard("city.db"); 123 | } 124 | }; 125 | 126 | new Thread(task).start(); 127 | } 128 | } 129 | 130 | /** 131 | * 拷贝资产目录下的文件到 手机 132 | */ 133 | private void copyAssetsFile2SDCard(String fileName) { 134 | 135 | File desDir = new File(APP_DIR); 136 | if (!desDir.exists()) { 137 | desDir.mkdirs(); 138 | } 139 | 140 | // 拷贝文件 141 | File file = new File(APP_DIR + fileName); 142 | if (file.exists()) { 143 | file.delete(); 144 | } 145 | 146 | try { 147 | InputStream in = getAssets().open(fileName); 148 | 149 | FileOutputStream fos = new FileOutputStream(file); 150 | 151 | int len = -1; 152 | byte[] buf = new byte[1024]; 153 | while ((len = in.read(buf)) > 0) { 154 | fos.write(buf, 0, len); 155 | } 156 | 157 | fos.flush(); 158 | fos.close(); 159 | 160 | handler.sendEmptyMessage(COPY_DB_SUCCESS); 161 | } catch (Exception e) { 162 | e.printStackTrace(); 163 | handler.sendEmptyMessage(COPY_DB_FAILED); 164 | } 165 | } 166 | 167 | /** 168 | * 从数据库中请求数据 169 | */ 170 | private void requestData() { 171 | 172 | Log.d(TAG_,"执行了requestData") ; 173 | 174 | Runnable task = new Runnable() { 175 | 176 | @Override 177 | public void run() { 178 | List hot = null; //热门城市 179 | List all = null; //全部城市 180 | try { 181 | hot = mDao.getHotCities(); 182 | all = mDao.getAllCities(); 183 | 184 | } catch (Exception e) { 185 | e.printStackTrace(); 186 | } 187 | 188 | if (all != null) { 189 | 190 | Collections.sort(all, new MyComparator()); //排序 191 | 192 | mCityList.addAll(hot); 193 | mCityList.addAll(all); 194 | 195 | //初始化每个字母有多少个item 196 | counts = new int[sections.length]; 197 | 198 | counts[0] = hot.size(); //热门城市 个数 199 | 200 | TreeSet cityFirstCharOfAbbreviation = new TreeSet() ; 201 | 202 | for (City city : all) { //计算全部城市 203 | //Note 城市缩写的第一个字母 204 | String firstCharacter = city.getSortKey(); 205 | cityFirstCharOfAbbreviation.add(firstCharacter) ; 206 | int index = ALL_CHARACTER.indexOf(firstCharacter); 207 | counts[index]++; 208 | } 209 | Log.w(TAG_,"所有城市缩写的第一个字母" + cityFirstCharOfAbbreviation.toString()) ; 210 | 211 | handler.sendEmptyMessage(QUERY_CITY_FINISH); 212 | } 213 | } 214 | }; 215 | 216 | new Thread(task).start(); 217 | } 218 | 219 | public class MyComparator implements Comparator { 220 | 221 | @Override 222 | public int compare(City c1, City c2) { 223 | 224 | return c1.getSortKey().compareTo(c2.getSortKey()); 225 | } 226 | 227 | } 228 | 229 | private void findView() { 230 | 231 | mListView = (PinnedHeaderListView) findViewById(R.id.mListView); 232 | BladeView mLetterListView = (BladeView) findViewById(R.id.mLetterListView); 233 | 234 | mLetterListView.setOnItemClickListener(new OnItemClickListener() { 235 | 236 | @Override 237 | public void onItemClick(String s) { 238 | if (s != null) { 239 | if (s.equalsIgnoreCase("当前")) { 240 | mListView.setSelection(0); 241 | return; 242 | } 243 | 244 | int section = ALL_CHARACTER.indexOf(s); 245 | 246 | int position = mIndexer.getPositionForSection(section); 247 | 248 | 249 | Log.i(TAG_, "s:" + s + ",section:" + section + ",position:" + position); 250 | 251 | if (position != -1) { 252 | mListView.setSelection(position); 253 | } else { 254 | 255 | } 256 | } 257 | 258 | } 259 | }); 260 | 261 | mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() { 262 | @Override 263 | public void onItemClick(AdapterView parent, View view, int position, long id) { 264 | Toast.makeText(getApplicationContext(), mCityList.get(position).getName(),100).show(); 265 | } 266 | }); 267 | } 268 | 269 | } 270 | -------------------------------------------------------------------------------- /src/com/example/pinnedheaderlistviewdemo/MySectionIndexer.java: -------------------------------------------------------------------------------- 1 | package com.example.pinnedheaderlistviewdemo; 2 | 3 | import android.util.Log; 4 | import android.widget.SectionIndexer; 5 | 6 | import java.util.Arrays; 7 | 8 | public class MySectionIndexer implements SectionIndexer { 9 | private final String[] mSections;// 10 | private final int[] mPositions; 11 | private final int mCount; 12 | 13 | /** 14 | * @param sections 15 | * @param counts 16 | */ 17 | public MySectionIndexer(String[] sections, int[] counts) { 18 | if (sections == null || counts == null) { 19 | throw new NullPointerException(); 20 | } 21 | if (sections.length != counts.length) { 22 | throw new IllegalArgumentException( 23 | "The sections and counts arrays must have the same length"); 24 | } 25 | this.mSections = sections; 26 | mPositions = new int[counts.length]; 27 | int position = 0; 28 | for (int i = 0; i < counts.length; i++) { 29 | if (mSections[i] == null) { 30 | mSections[i] = ""; 31 | } else { 32 | mSections[i] = mSections[i].trim(); 33 | } 34 | 35 | mPositions[i] = position; 36 | position += counts[i]; 37 | 38 | Log.i("MySectionIndexer", "counts[" + i + "]:" + counts[i]); 39 | } 40 | mCount = position; 41 | } 42 | 43 | @Override 44 | public Object[] getSections() { 45 | return mSections; 46 | } 47 | 48 | @Override 49 | public int getPositionForSection(int section) { 50 | //change by lcq 2012-10-12 section > mSections.length以为>= 51 | if (section < 0 || section >= mSections.length) { 52 | return -1; 53 | } 54 | return mPositions[section]; 55 | } 56 | 57 | @Override 58 | public int getSectionForPosition(int position) { 59 | if (position < 0 || position >= mCount) { 60 | return -1; 61 | } 62 | //注意这个方法的返回值,它就是index<0时,返回-index-2的原因 63 | //解释Arrays.binarySearch,如果搜索结果在数组中,刚返回它在数组中的索引,如果不在,刚返回第一个比它大的索引的负数-1 64 | //如果没弄明白,请自己想查看api 65 | int index = Arrays.binarySearch(mPositions, position); 66 | return index >= 0 ? index : -index - 2; //当index小于0时,返回-index-2, 67 | 68 | } 69 | 70 | } -------------------------------------------------------------------------------- /src/com/example/pinnedheaderlistviewdemo/adapter/CityListAdapter.java: -------------------------------------------------------------------------------- 1 | package com.example.pinnedheaderlistviewdemo.adapter; 2 | 3 | import java.util.List; 4 | 5 | import android.content.Context; 6 | import android.view.LayoutInflater; 7 | import android.view.View; 8 | import android.view.ViewGroup; 9 | import android.widget.AbsListView; 10 | import android.widget.AbsListView.OnScrollListener; 11 | import android.widget.BaseAdapter; 12 | import android.widget.TextView; 13 | 14 | import com.example.pinnedheaderlistviewdemo.City; 15 | import com.example.pinnedheaderlistviewdemo.MySectionIndexer; 16 | import com.example.pinnedheaderlistviewdemo.R; 17 | import com.example.pinnedheaderlistviewdemo.view.PinnedHeaderListView; 18 | import com.example.pinnedheaderlistviewdemo.view.PinnedHeaderListView.PinnedHeaderAdapter; 19 | 20 | public class CityListAdapter extends BaseAdapter implements 21 | PinnedHeaderAdapter, OnScrollListener { 22 | private List mList; 23 | private MySectionIndexer mIndexer; 24 | private Context mContext; 25 | private int mLocationPosition = -1; 26 | private LayoutInflater mInflater; 27 | 28 | public CityListAdapter(List mList, MySectionIndexer mIndexer, 29 | Context mContext) { 30 | this.mList = mList; 31 | this.mIndexer = mIndexer; 32 | this.mContext = mContext; 33 | mInflater = LayoutInflater.from(mContext); 34 | } 35 | 36 | @Override 37 | public int getCount() { 38 | return mList == null ? 0 : mList.size(); 39 | } 40 | 41 | @Override 42 | public Object getItem(int position) { 43 | return mList.get(position); 44 | } 45 | 46 | @Override 47 | public long getItemId(int position) { 48 | return position; 49 | } 50 | 51 | @Override 52 | public View getView(int position, View convertView, ViewGroup parent) { 53 | View view; 54 | ViewHolder holder; 55 | if (convertView == null) { 56 | view = mInflater.inflate(R.layout.select_city_item, null); 57 | 58 | holder = new ViewHolder(); 59 | holder.group_title = (TextView) view.findViewById(R.id.group_title); 60 | holder.city_name = (TextView) view.findViewById(R.id.city_name); 61 | 62 | view.setTag(holder); 63 | } else { 64 | view = convertView; 65 | holder = (ViewHolder) view.getTag(); 66 | } 67 | 68 | City city = mList.get(position); 69 | 70 | int section = mIndexer.getSectionForPosition(position); 71 | if (mIndexer.getPositionForSection(section) == position) { 72 | holder.group_title.setVisibility(View.VISIBLE); 73 | holder.group_title.setText(city.getSortKey()); 74 | } else { 75 | holder.group_title.setVisibility(View.GONE); 76 | } 77 | 78 | holder.city_name.setText(city.getName()); 79 | 80 | return view; 81 | } 82 | 83 | public static class ViewHolder { 84 | public TextView group_title; 85 | public TextView city_name; 86 | } 87 | 88 | @Override 89 | public int getPinnedHeaderState(int position) { 90 | int realPosition = position; 91 | if (realPosition < 0 92 | || (mLocationPosition != -1 && mLocationPosition == realPosition)) { 93 | return PINNED_HEADER_GONE; 94 | } 95 | mLocationPosition = -1; 96 | int section = mIndexer.getSectionForPosition(realPosition); 97 | int nextSectionPosition = mIndexer.getPositionForSection(section + 1); 98 | if (nextSectionPosition != -1 99 | && realPosition == nextSectionPosition - 1) { 100 | return PINNED_HEADER_PUSHED_UP; 101 | } 102 | return PINNED_HEADER_VISIBLE; 103 | } 104 | 105 | @Override 106 | public void configurePinnedHeader(View header, int position, int alpha) { 107 | // TODO Auto-generated method stub 108 | int realPosition = position; 109 | int section = mIndexer.getSectionForPosition(realPosition); 110 | String title = (String) mIndexer.getSections()[section]; 111 | ((TextView) header.findViewById(R.id.group_title)).setText(title); 112 | } 113 | 114 | @Override 115 | public void onScrollStateChanged(AbsListView view, int scrollState) { 116 | // TODO Auto-generated method stub 117 | 118 | } 119 | 120 | @Override 121 | public void onScroll(AbsListView view, int firstVisibleItem, 122 | int visibleItemCount, int totalItemCount) { 123 | // TODO Auto-generated method stub 124 | if (view instanceof PinnedHeaderListView) { 125 | ((PinnedHeaderListView) view).configureHeaderView(firstVisibleItem); 126 | } 127 | 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /src/com/example/pinnedheaderlistviewdemo/db/CityDao.java: -------------------------------------------------------------------------------- 1 | package com.example.pinnedheaderlistviewdemo.db; 2 | 3 | import android.database.Cursor; 4 | import android.database.sqlite.SQLiteDatabase; 5 | import android.util.Log; 6 | import com.example.pinnedheaderlistviewdemo.City; 7 | import com.example.pinnedheaderlistviewdemo.MainActivity; 8 | import org.apache.commons.lang3.ArrayUtils; 9 | 10 | import java.util.ArrayList; 11 | import java.util.List; 12 | import java.util.TreeSet; 13 | 14 | public class CityDao implements DBHelper.TableData { 15 | private static final String TAG = "CityDao" ; 16 | 17 | private DBHelper helper; 18 | 19 | private String[] mColumns = {CityTable.id,CityTable.name,CityTable.pyf,CityTable.pys} ; 20 | private String mSelection = " hot = ? " ; 21 | private String[] mSelectionArgsHotCities = {"2"} ; 22 | 23 | /** 24 | *

25 | * 因为热门城市和全部城市,一般不会改变,所以将其设置为静态的 26 | *

27 | */ 28 | private static List mHotCities; //热门城市 29 | private static List mAllCities; //全部城市 30 | private static TreeSet mFirstCharFromAbbreviationOfCity; //所有城市缩写的首字母 31 | 32 | private static int mPreCharNum;//当前 热门等选项的个数 33 | 34 | 35 | 36 | public CityDao(DBHelper helper) { 37 | this.helper = helper; 38 | mHotCities = getHotCitiesFromDB() ; 39 | mAllCities = getAllCitiesFromDB() ; 40 | getFirstCharFromAbbreviationOfCity() ; 41 | } 42 | 43 | 44 | /** 45 | * 从数据库中获取热门城市 46 | * @return 47 | */ 48 | private List getHotCitiesFromDB(){ 49 | SQLiteDatabase db = helper.getReadableDataBase(MainActivity.APP_DIR, "city.db"); 50 | 51 | List list = new ArrayList(); 52 | 53 | Cursor cursor = null; 54 | 55 | try { 56 | if (db.isOpen()) { 57 | 58 | //old 59 | //: 60 | // String sql = "SELECT id,name,pyf,pys FROM city where hot = 2"; 61 | // cursor = db.rawQuery(sql, null); 62 | //~ 63 | //new 改用android自带的形式,替换sqlraw形式 64 | cursor = db.query(CityTable.table,mColumns,mSelection, mSelectionArgsHotCities,null,null,null,null) ; 65 | 66 | while (cursor.moveToNext()) { 67 | 68 | City city = new City(); 69 | city.setId(cursor.getString(0)); 70 | city.setName(cursor.getString(1)); 71 | city.setPyf(cursor.getString(2)); 72 | city.setPys(cursor.getString(3)); 73 | 74 | list.add(city); 75 | } 76 | } 77 | } finally { 78 | if (cursor != null) { 79 | cursor.close(); 80 | } 81 | db.close(); 82 | } 83 | 84 | return list; 85 | } 86 | /** 87 | * 获取所有热门城市 88 | * @return 89 | */ 90 | public List getHotCities() throws Exception { 91 | if (mHotCities == null) { 92 | //TODO 实际上只要初始化了本类,就不会运行到此处 93 | throw new Exception("数据库中的热门城市为空") ; 94 | } 95 | return mHotCities; 96 | // SQLiteDatabase db = helper.getReadableDataBase(MainActivity.APP_DIR, "city.db"); 97 | // 98 | // List list = new ArrayList(); 99 | // 100 | // Cursor cursor = null; 101 | // 102 | // try { 103 | // if (db.isOpen()) { 104 | // 105 | // //old 106 | // //: 107 | //// String sql = "SELECT id,name,pyf,pys FROM city where hot = 2"; 108 | //// cursor = db.rawQuery(sql, null); 109 | // //~ 110 | // //new 改用android自带的形式,替换sqlraw形式 111 | // cursor = db.query(CityTable.table,mColumns,mSelection, mSelectionArgsHotCities,null,null,null,null) ; 112 | // 113 | // while (cursor.moveToNext()) { 114 | // 115 | // City city = new City(); 116 | // city.setId(cursor.getString(0)); 117 | // city.setName(cursor.getString(1)); 118 | // city.setPyf(cursor.getString(2)); 119 | // city.setPys(cursor.getString(3)); 120 | // 121 | // list.add(city); 122 | // } 123 | // } 124 | // } finally { 125 | // if (cursor != null) { 126 | // cursor.close(); 127 | // } 128 | // db.close(); 129 | // } 130 | // 131 | // return list; 132 | } 133 | 134 | /** 135 | * 从数据库中获取全部城市 136 | * @return 137 | */ 138 | private List getAllCitiesFromDB(){ 139 | SQLiteDatabase db = helper.getReadableDataBase(MainActivity.APP_DIR, "city.db"); 140 | 141 | List list = new ArrayList(); 142 | 143 | Cursor cursor = null; 144 | 145 | try { 146 | if (db.isOpen()) { 147 | // String sql = "SELECT id,name,pyf,pys FROM city"; 148 | // cursor = db.rawQuery(sql, null); 149 | cursor = db.query(CityTable.table,mColumns,null,null,null,null,null,null) ; 150 | while (cursor.moveToNext()) { 151 | 152 | City city = new City(); 153 | city.setId(cursor.getString(0)); 154 | city.setName(cursor.getString(1)); 155 | city.setPyf(cursor.getString(2)); 156 | city.setPys(cursor.getString(3)); 157 | 158 | list.add(city); 159 | } 160 | } 161 | } finally { 162 | if (cursor != null) { 163 | cursor.close(); 164 | } 165 | db.close(); 166 | } 167 | 168 | return list; 169 | } 170 | /** 171 | * 获取数据库中的所有城市 172 | * @return 173 | */ 174 | public List getAllCities() throws Exception { 175 | if (mAllCities == null) { 176 | throw new Exception("数据库中全部城市为空") ; 177 | } 178 | return mAllCities; 179 | // SQLiteDatabase db = helper.getReadableDataBase(MainActivity.APP_DIR, "city.db"); 180 | // 181 | // List list = new ArrayList(); 182 | // 183 | // Cursor cursor = null; 184 | // 185 | // try { 186 | // if (db.isOpen()) { 187 | //// String sql = "SELECT id,name,pyf,pys FROM city"; 188 | //// cursor = db.rawQuery(sql, null); 189 | // cursor = db.query(CityTable.table,mColumns,null,null,null,null,null,null) ; 190 | // while (cursor.moveToNext()) { 191 | // 192 | // City city = new City(); 193 | // city.setId(cursor.getString(0)); 194 | // city.setName(cursor.getString(1)); 195 | // city.setPyf(cursor.getString(2)); 196 | // city.setPys(cursor.getString(3)); 197 | // 198 | // list.add(city); 199 | // } 200 | // } 201 | // } finally { 202 | // if (cursor != null) { 203 | // cursor.close(); 204 | // } 205 | // db.close(); 206 | // } 207 | // 208 | // return list; 209 | } 210 | 211 | /** 212 | * 获取所有城市缩写的首字母, 213 | * 例如 214 | *

215 | * 所有城市缩写的第一个字母[A, B, C, D, F, G, H, J, K, L, M, N, P, Q, R, S, T, W, X, Y, Z] 216 | *

217 | * @return 218 | */ 219 | private TreeSet getFirstCharFromAbbreviationOfCity() { 220 | if (mFirstCharFromAbbreviationOfCity == null) { 221 | mFirstCharFromAbbreviationOfCity = new TreeSet() ; 222 | for (City city : mAllCities) { //计算全部城市 223 | //Note 城市缩写的第一个字母 224 | String firstCharacter = city.getSortKey(); 225 | mFirstCharFromAbbreviationOfCity.add(firstCharacter); 226 | } 227 | } 228 | 229 | return mFirstCharFromAbbreviationOfCity; 230 | } 231 | 232 | /** 233 | * 可变参数 234 | * @param presentLocation 当前位置 235 | * hotCities 热门城市 236 | * 237 | * @return 238 | * 返回结果类似与以下 239 | *

240 | * {"当前", "热门", "A", "B", "C", "D", "F", "G", "H", "J", "K", 241 | * "L", "M", "N", "O", "P", "Q", "R", "S", "T", "W", "X", 242 | * "Y", "Z"}; 243 | *

244 | */ 245 | public String[] sectionsAndBlade(String... presentLocation){ 246 | mPreCharNum = presentLocation.length ; 247 | String [] sectionsAndBlade ; 248 | sectionsAndBlade = ArrayUtils.addAll( 249 | presentLocation, 250 | mFirstCharFromAbbreviationOfCity.toArray(new String[mFirstCharFromAbbreviationOfCity.size()])) ; 251 | for (String string : sectionsAndBlade) { 252 | Log.d(TAG,string) ; 253 | } 254 | return sectionsAndBlade ; 255 | } 256 | // public static final String ALL_CHARACTER = "#ABCDFGHJKLMNOPQRSTWXYZ"; 257 | public String allCharacter(){ 258 | StringBuilder stringBuilder = new StringBuilder() ; 259 | for (int i = 0; i < mPreCharNum; i++) { 260 | stringBuilder.append("#") ; 261 | } 262 | for (String s : mFirstCharFromAbbreviationOfCity) { 263 | stringBuilder.append(s) ; 264 | } 265 | return stringBuilder.toString() ; 266 | 267 | } 268 | } -------------------------------------------------------------------------------- /src/com/example/pinnedheaderlistviewdemo/db/DBHelper.java: -------------------------------------------------------------------------------- 1 | package com.example.pinnedheaderlistviewdemo.db; 2 | 3 | import android.database.sqlite.SQLiteDatabase; 4 | 5 | import java.util.concurrent.locks.Lock; 6 | import java.util.concurrent.locks.ReadWriteLock; 7 | import java.util.concurrent.locks.ReentrantReadWriteLock; 8 | 9 | public class DBHelper { 10 | 11 | private static final String TAG = "DBHelper" ; 12 | 13 | /** 14 | * 数据库相关的所有表 15 | */ 16 | public interface TableData{ 17 | /** 18 | * 城市表 19 | */ 20 | public interface CityTable{ 21 | String table = "city" ; 22 | String id ="id" ; 23 | String name ="name" ; 24 | String pyf ="pyf" ; 25 | String pys ="pys" ; 26 | String hot ="hot" ; 27 | } 28 | } 29 | 30 | private ReadWriteLock lock = new ReentrantReadWriteLock(true); 31 | private Lock readLock = lock.readLock(); 32 | private Lock writeLock = lock.writeLock(); 33 | 34 | /** 35 | * 从指定数据库文件获取一个可读的 SQLiteDatabase 36 | * @param dbDirPath 37 | * @param dbFileName 38 | * @return 39 | */ 40 | public SQLiteDatabase getReadableDataBase(String dbDirPath, String dbFileName) { 41 | 42 | readLock.lock(); 43 | 44 | try { 45 | String dbPath = dbDirPath.concat(dbFileName); 46 | 47 | return SQLiteDatabase.openDatabase(dbPath, null, SQLiteDatabase.NO_LOCALIZED_COLLATORS); 48 | } finally { 49 | readLock.unlock(); 50 | } 51 | } 52 | 53 | /** 54 | * 从指定数据库文件获取一个以读写的 SQLiteDatabase 55 | * @param dbDirPath 56 | * @param dbFileName 57 | * @return 58 | */ 59 | public SQLiteDatabase getWritableDataBase(String dbDirPath, String dbFileName) { 60 | 61 | writeLock.lock(); 62 | 63 | try { 64 | String dbPath = dbDirPath.concat(dbFileName); 65 | 66 | return SQLiteDatabase.openDatabase(dbPath, null, SQLiteDatabase.NO_LOCALIZED_COLLATORS); 67 | } finally { 68 | writeLock.unlock(); 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/com/example/pinnedheaderlistviewdemo/view/BladeView.java: -------------------------------------------------------------------------------- 1 | package com.example.pinnedheaderlistviewdemo.view; 2 | 3 | import android.content.Context; 4 | import android.graphics.Canvas; 5 | import android.graphics.Color; 6 | import android.graphics.Paint; 7 | import android.os.Handler; 8 | import android.util.AttributeSet; 9 | import android.util.Log; 10 | import android.view.Gravity; 11 | import android.view.MotionEvent; 12 | import android.view.View; 13 | import android.widget.PopupWindow; 14 | import android.widget.TextView; 15 | import com.example.pinnedheaderlistviewdemo.MainActivity; 16 | import com.example.pinnedheaderlistviewdemo.R; 17 | import com.example.pinnedheaderlistviewdemo.db.CityDao; 18 | import com.example.pinnedheaderlistviewdemo.db.DBHelper; 19 | 20 | /** 21 | * 竖向选择器 字母索引 22 | */ 23 | public class BladeView extends View { 24 | private static final String TAG ="BladeView" ; 25 | 26 | private OnItemClickListener mOnItemClickListener; 27 | private static String[] mBlade ; 28 | // = {"当前", "A", "B", "C", "D", "F", "G", "H", "J", "K", 29 | // "L", "M", "N", "O", "P", "Q", "R", "S", "T", "W", "X", 30 | // "Y", "Z"}; 31 | int choose = -1; 32 | Paint paint = new Paint(); 33 | boolean showBkg = false; 34 | private PopupWindow mPopupWindow; 35 | private TextView mPopupText; 36 | private Handler handler = new Handler(); 37 | 38 | { 39 | mBlade = new CityDao(new DBHelper()).sectionsAndBlade("当前"); 40 | } 41 | public BladeView(Context context, AttributeSet attrs, int defStyle) { 42 | super(context, attrs, defStyle); 43 | } 44 | 45 | public BladeView(Context context, AttributeSet attrs) { 46 | super(context, attrs); 47 | } 48 | 49 | public BladeView(Context context) { 50 | super(context); 51 | } 52 | 53 | @Override 54 | protected void onDraw(Canvas canvas) { 55 | super.onDraw(canvas); 56 | if (showBkg) { 57 | canvas.drawColor(Color.parseColor("#AAAAAA")); 58 | } 59 | 60 | int height = getHeight(); 61 | int width = getWidth(); 62 | int singleHeight = height / mBlade.length; 63 | for (int i = 0; i < mBlade.length; i++) { 64 | //竖向选择器中字母默认显示的颜色 65 | paint.setColor(Color.parseColor("#ff2f2f2f")); 66 | // paint.setTypeface(Typeface.DEFAULT_BOLD); //加粗 67 | paint.setTextSize(getResources().getDimensionPixelSize(R.dimen.bladeview_fontsize));//设置字体的大小 68 | paint.setFakeBoldText(true); 69 | paint.setAntiAlias(true); 70 | if (i == choose) { 71 | //竖向选择器中 被选中字母的颜色 72 | paint.setColor(Color.parseColor("#ff0000")); 73 | // paint.setColor(Color.parseColor("#3399ff")); 74 | } 75 | float xPos = width / 2 - paint.measureText(mBlade[i]) / 2; 76 | float yPos = singleHeight * i + singleHeight; 77 | canvas.drawText(mBlade[i], xPos, yPos, paint); 78 | paint.reset(); 79 | } 80 | 81 | } 82 | 83 | @Override 84 | public boolean dispatchTouchEvent(MotionEvent event) { 85 | final int action = event.getAction(); 86 | final float y = event.getY(); 87 | final int oldChoose = choose; 88 | //用户点击选中的字母 89 | final int choiceLetter = (int) (y / getHeight() * mBlade.length); 90 | 91 | switch (action) { 92 | case MotionEvent.ACTION_DOWN: 93 | //如果此处设置为true,意为当竖向选择器被点击(包括滑动状态)时,显示背景颜色 94 | // showBkg = true; 95 | if (oldChoose != choiceLetter) { 96 | if (choiceLetter >= 0 && choiceLetter < mBlade.length) { //让第一个字母响应点击事件 97 | performItemClicked(choiceLetter); 98 | choose = choiceLetter; 99 | invalidate(); 100 | } 101 | } 102 | 103 | break; 104 | case MotionEvent.ACTION_MOVE: 105 | if (oldChoose != choiceLetter) { 106 | if (choiceLetter >= 0 && choiceLetter < mBlade.length) { //让第一个字母响应点击事件 107 | performItemClicked(choiceLetter); 108 | choose = choiceLetter; 109 | /* 110 | invalidate: 111 | Invalidate[ɪn'vælɪdeɪt] the whole view. If the view is visible, 112 | onDraw(android.graphics.Canvas) will be called at some point in 113 | the future. This must be called from a UI thread. To call from a non-UI thread, 114 | call postInvalidate(). 115 | */ 116 | invalidate(); 117 | } 118 | } 119 | break; 120 | case MotionEvent.ACTION_UP: 121 | showBkg = false; 122 | choose = -1; 123 | dismissPopup(); 124 | invalidate(); 125 | break; 126 | } 127 | return true; 128 | } 129 | 130 | /** 131 | * 弹出显示 字母索引 132 | * @param item 133 | */ 134 | private void showPopup(int item) { 135 | if (mPopupWindow == null) { 136 | 137 | handler.removeCallbacks(dismissRunnable); 138 | mPopupText = new TextView(getContext()); 139 | mPopupText.setBackgroundColor(Color.GRAY); 140 | mPopupText.setTextColor(Color.WHITE); 141 | mPopupText.setTextSize(getResources().getDimensionPixelSize(R.dimen.bladeview_popup_fontsize)); 142 | mPopupText.setGravity(Gravity.CENTER_HORIZONTAL 143 | | Gravity.CENTER_VERTICAL); 144 | 145 | int height = getResources().getDimensionPixelSize(R.dimen.bladeview_popup_height); 146 | 147 | mPopupWindow = new PopupWindow(mPopupText, height, height); 148 | } 149 | 150 | String text = ""; 151 | if (item == 0) { 152 | text = "当前"; 153 | } else { 154 | text = MainActivity.ALL_CHARACTER.substring(item, item + 1) ; 155 | // text = Character.toString((char) ('A' + item - 1)); 156 | } 157 | mPopupText.setText(text); 158 | if (mPopupWindow.isShowing()) { 159 | mPopupWindow.update(); 160 | } else { 161 | mPopupWindow.showAtLocation(getRootView(), 162 | Gravity.CENTER_HORIZONTAL | Gravity.CENTER_VERTICAL, 0, 0); 163 | } 164 | } 165 | 166 | private void dismissPopup() { 167 | handler.postDelayed(dismissRunnable, 800); 168 | } 169 | 170 | Runnable dismissRunnable = new Runnable() { 171 | 172 | @Override 173 | public void run() { 174 | // TODO Auto-generated method stub 175 | if (mPopupWindow != null) { 176 | mPopupWindow.dismiss(); 177 | } 178 | } 179 | }; 180 | 181 | public boolean onTouchEvent(MotionEvent event) { 182 | return super.onTouchEvent(event); 183 | } 184 | 185 | public void setOnItemClickListener(OnItemClickListener listener) { 186 | mOnItemClickListener = listener; 187 | } 188 | 189 | private void performItemClicked(int item) { 190 | if (mOnItemClickListener != null) { 191 | Log.d(TAG,"item :" + item + "对应的字母为" + mBlade[item]) ; 192 | mOnItemClickListener.onItemClick(mBlade[item]); 193 | showPopup(item); 194 | } 195 | } 196 | 197 | public interface OnItemClickListener { 198 | void onItemClick(String s); 199 | } 200 | 201 | } 202 | -------------------------------------------------------------------------------- /src/com/example/pinnedheaderlistviewdemo/view/PinnedHeaderListView.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.example.pinnedheaderlistviewdemo.view; 18 | 19 | import android.content.Context; 20 | import android.graphics.Canvas; 21 | import android.util.AttributeSet; 22 | import android.view.View; 23 | import android.widget.ListAdapter; 24 | import android.widget.ListView; 25 | 26 | /** 27 | * A ListView that maintains a header pinned at the top of the list. The 28 | * pinned header can be pushed up and dissolved as needed. 29 | */ 30 | public class PinnedHeaderListView extends ListView { 31 | 32 | /** 33 | * Adapter interface. The list adapter must implement this interface. 34 | */ 35 | public interface PinnedHeaderAdapter { 36 | 37 | /** 38 | * Pinned header state: don't show the header. 39 | */ 40 | public static final int PINNED_HEADER_GONE = 0; 41 | 42 | /** 43 | * Pinned header state: show the header at the top of the list. 44 | */ 45 | public static final int PINNED_HEADER_VISIBLE = 1; 46 | 47 | /** 48 | * Pinned header state: show the header. If the header extends beyond 49 | * the bottom of the first shown element, push it up and clip. 50 | */ 51 | public static final int PINNED_HEADER_PUSHED_UP = 2; 52 | 53 | /** 54 | * Computes the desired state of the pinned header for the given 55 | * position of the first visible list item. Allowed return values are 56 | * {@link #PINNED_HEADER_GONE}, {@link #PINNED_HEADER_VISIBLE} or 57 | * {@link #PINNED_HEADER_PUSHED_UP}. 58 | */ 59 | int getPinnedHeaderState(int position); 60 | 61 | /** 62 | * Configures the pinned header view to match the first visible list item. 63 | * 64 | * @param header pinned header view. 65 | * @param position position of the first visible list item. 66 | * @param alpha fading of the header view, between 0 and 255. 67 | */ 68 | void configurePinnedHeader(View header, int position, int alpha); 69 | } 70 | 71 | private static final int MAX_ALPHA = 255; 72 | 73 | private PinnedHeaderAdapter mAdapter; 74 | private View mHeaderView; 75 | private boolean mHeaderViewVisible; 76 | 77 | private int mHeaderViewWidth; 78 | 79 | private int mHeaderViewHeight; 80 | 81 | public PinnedHeaderListView(Context context) { 82 | super(context); 83 | } 84 | 85 | public PinnedHeaderListView(Context context, AttributeSet attrs) { 86 | super(context, attrs); 87 | } 88 | 89 | public PinnedHeaderListView(Context context, AttributeSet attrs, int defStyle) { 90 | super(context, attrs, defStyle); 91 | } 92 | 93 | public void setPinnedHeaderView(View view) { 94 | mHeaderView = view; 95 | 96 | // Disable vertical fading when the pinned header is present 97 | // TODO change ListView to allow separate measures for top and bottom fading edge; 98 | // in this particular case we would like to disable the top, but not the bottom edge. 99 | if (mHeaderView != null) { 100 | setFadingEdgeLength(0); 101 | } 102 | requestLayout(); 103 | } 104 | 105 | @Override 106 | public void setAdapter(ListAdapter adapter) { 107 | super.setAdapter(adapter); 108 | mAdapter = (PinnedHeaderAdapter) adapter; 109 | } 110 | 111 | @Override 112 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 113 | super.onMeasure(widthMeasureSpec, heightMeasureSpec); 114 | if (mHeaderView != null) { 115 | measureChild(mHeaderView, widthMeasureSpec, heightMeasureSpec); 116 | mHeaderViewWidth = mHeaderView.getMeasuredWidth(); 117 | mHeaderViewHeight = mHeaderView.getMeasuredHeight(); 118 | } 119 | } 120 | 121 | @Override 122 | protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 123 | super.onLayout(changed, left, top, right, bottom); 124 | if (mHeaderView != null) { 125 | mHeaderView.layout(0, 0, mHeaderViewWidth, mHeaderViewHeight); 126 | configureHeaderView(getFirstVisiblePosition()); 127 | } 128 | } 129 | 130 | public void configureHeaderView(int position) { 131 | if (mHeaderView == null) { 132 | return; 133 | } 134 | 135 | int state = mAdapter.getPinnedHeaderState(position); 136 | switch (state) { 137 | case PinnedHeaderAdapter.PINNED_HEADER_GONE: { 138 | mHeaderViewVisible = false; 139 | break; 140 | } 141 | 142 | case PinnedHeaderAdapter.PINNED_HEADER_VISIBLE: { 143 | mAdapter.configurePinnedHeader(mHeaderView, position, MAX_ALPHA); 144 | if (mHeaderView.getTop() != 0) { 145 | mHeaderView.layout(0, 0, mHeaderViewWidth, mHeaderViewHeight); 146 | } 147 | mHeaderViewVisible = true; 148 | break; 149 | } 150 | 151 | case PinnedHeaderAdapter.PINNED_HEADER_PUSHED_UP: { 152 | View firstView = getChildAt(0); 153 | int bottom = firstView.getBottom(); 154 | int itemHeight = firstView.getHeight(); 155 | int headerHeight = mHeaderView.getHeight(); 156 | int y; 157 | int alpha; 158 | if (bottom < headerHeight) { 159 | y = (bottom - headerHeight); 160 | alpha = MAX_ALPHA * (headerHeight + y) / headerHeight; 161 | } else { 162 | y = 0; 163 | alpha = MAX_ALPHA; 164 | } 165 | mAdapter.configurePinnedHeader(mHeaderView, position, alpha); 166 | if (mHeaderView.getTop() != y) { 167 | mHeaderView.layout(0, y, mHeaderViewWidth, mHeaderViewHeight + y); 168 | } 169 | mHeaderViewVisible = true; 170 | break; 171 | } 172 | } 173 | } 174 | 175 | @Override 176 | protected void dispatchDraw(Canvas canvas) { 177 | super.dispatchDraw(canvas); 178 | if (mHeaderViewVisible) { 179 | drawChild(canvas, mHeaderView, getDrawingTime()); 180 | } 181 | } 182 | } 183 | --------------------------------------------------------------------------------