效果图如下:
三个要点:
1.排序:包括汉字,字母,特殊符号
2.listview的实现:自定义adapter,以及listview的效果实现
3.侧边字母集的实现
1.排序
实现使用android源生中联系人排序代码,sortkey,关键在于汉字转拼音
如下:
/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import android.text.TextUtils;
import android.util.Log;
import java.text.Collator;
import java.util.ArrayList;
import java.util.Locale;
/**
* An object to convert Chinese character to its corresponding pinyin string. For characters with
* multiple possible pinyin string, only one is selected according to collator. Polyphone is not
* supported in this implementation. This class is implemented to achieve the best runtime
* performance and minimum runtime resources with tolerable sacrifice of accuracy. This
* implementation highly depends on zh_CN ICU collation data and must be always synchronized with
* ICU.
* <p/>
* Currently this file is aligned to zh.txt in ICU 4.6
*/
public class HanziToPinyin {
private static final String TAG = "HanziToPinyin";
// Turn on this flag when we want to check internal data structure.
private static final boolean DEBUG = false;
/**
* Unihans array.
* <p/>
* Each unihans is the first one within same pinyin when collator is zh_CN.
*/
public static final char[] UNIHANS = {
'\u963f', '\u54ce', '\u5b89', '\u80ae', '\u51f9', '\u516b',
'\u6300', '\u6273', '\u90a6', '\u52f9', '\u9642', '\u5954',
'\u4f3b', '\u5c44', '\u8fb9', '\u706c', '\u618b', '\u6c43',
'\u51ab', '\u7676', '\u5cec', '\u5693', '\u5072', '\u53c2',
'\u4ed3', '\u64a1', '\u518a', '\u5d7e', '\u66fd', '\u66fe',
'\u5c64', '\u53c9', '\u8286', '\u8fbf', '\u4f25', '\u6284',
'\u8f66', '\u62bb', '\u6c88', '\u6c89', '\u9637', '\u5403',
'\u5145', '\u62bd', '\u51fa', '\u6b3b', '\u63e3', '\u5ddb',
'\u5205', '\u5439', '\u65fe', '\u9034', '\u5472', '\u5306',
'\u51d1', '\u7c97', '\u6c46', '\u5d14', '\u90a8', '\u6413',
'\u5491', '\u5446', '\u4e39', '\u5f53', '\u5200', '\u561a',
'\u6265', '\u706f', '\u6c10', '\u55f2', '\u7538', '\u5201',
'\u7239', '\u4e01', '\u4e1f', '\u4e1c', '\u543a', '\u53be',
'\u8011', '\u8968', '\u5428', '\u591a', '\u59b8', '\u8bf6',
'\u5940', '\u97a5', '\u513f', '\u53d1', '\u5e06', '\u531a',
'\u98de', '\u5206', '\u4e30', '\u8985', '\u4ecf', '\u7d11',
'\u4f15', '\u65ee', '\u4f85', '\u7518', '\u5188', '\u768b',
'\u6208', '\u7ed9', '\u6839', '\u522f', '\u5de5', '\u52fe',
'\u4f30', '\u74dc', '\u4e56', '\u5173', '\u5149', '\u5f52',
'\u4e28', '\u5459', '\u54c8', '\u548d', '\u4f44', '\u592f',
'\u8320', '\u8bc3', '\u9ed2', '\u62eb', '\u4ea8', '\u5677',
'\u53ff', '\u9f41', '\u4e6f', '\u82b1', '\u6000', '\u72bf',
'\u5ddf', '\u7070', '\u660f', '\u5419', '\u4e0c', '\u52a0',
'\u620b', '\u6c5f', '\u827d', '\u9636', '\u5dfe', '\u5755',
'\u5182', '\u4e29', '\u51e5', '\u59e2', '\u5658', '\u519b',
'\u5494', '\u5f00', '\u520a', '\u5ffc', '\u5c3b', '\u533c',
'\u808e', '\u52a5', '\u7a7a', '\u62a0', '\u625d', '\u5938',
'\u84af', '\u5bbd', '\u5321', '\u4e8f', '\u5764', '\u6269',
'\u5783', '\u6765', '\u5170', '\u5577', '\u635e', '\u808b',
'\u52d2', '\u5d1a', '\u5215', '\u4fe9', '\u5941', '\u826f',
'\u64a9', '\u5217', '\u62ce', '\u5222', '\u6e9c', '\u56d6',
'\u9f99', '\u779c', '\u565c', '\u5a08', '\u7567', '\u62a1',
'\u7f57', '\u5463', '\u5988', '\u57cb', '\u5ada', '\u7264',
'\u732b', '\u4e48', '\u5445', '\u95e8', '\u753f', '\u54aa',
'\u5b80', '\u55b5', '\u4e5c', '\u6c11', '\u540d', '\u8c2c',
'\u6478', '\u54de', '\u6bea', '\u55ef', '\u62cf', '\u8149',
'\u56e1', '\u56d4', '\u5b6c', '\u7592', '\u5a1e', '\u6041',
'\u80fd', '\u59ae', '\u62c8', '\u5b22', '\u9e1f', '\u634f',
'\u56dc', '\u5b81', '\u599e', '\u519c', '\u7fba', '\u5974',
'\u597b', '\u759f', '\u9ec1', '\u90cd', '\u5594', '\u8bb4',
'\u5991', '\u62cd', '\u7705', '\u4e53', '\u629b', '\u5478',
'\u55b7', '\u5309', '\u4e15', '\u56e8', '\u527d', '\u6c15',
'\u59d8', '\u4e52', '\u948b', '\u5256', '\u4ec6', '\u4e03',
'\u6390', '\u5343', '\u545b', '\u6084', '\u767f', '\u4eb2',
'\u72c5', '\u828e', '\u4e18', '\u533a', '\u5cd1', '\u7f3a',
'\u590b', '\u5465', '\u7a63', '\u5a06', '\u60f9', '\u4eba',
'\u6254', '\u65e5', '\u8338', '\u53b9', '\u909a', '\u633c',
'\u5827', '\u5a51', '\u77a4', '\u637c', '\u4ee8', '\u6be2',
'\u4e09', '\u6852', '\u63bb', '\u95aa', '\u68ee', '\u50e7',
'\u6740', '\u7b5b', '\u5c71', '\u4f24', '\u5f30', '\u5962',
'\u7533', '\u8398', '\u6552', '\u5347', '\u5c38', '\u53ce',
'\u4e66', '\u5237', '\u8870', '\u95e9', '\u53cc', '\u8c01',
'\u542e', '\u8bf4', '\u53b6', '\u5fea', '\u635c', '\u82cf',
'\u72fb', '\u590a', '\u5b59', '\u5506', '\u4ed6', '\u56fc',
'\u574d', '\u6c64', '\u5932', '\u5fd1', '\u71a5', '\u5254',
'\u5929', '\u65eb', '\u5e16', '\u5385', '\u56f2', '\u5077',
'\u51f8', '\u6e4d', '\u63a8', '\u541e', '\u4e47', '\u7a75',
'\u6b6a', '\u5f2f', '\u5c23', '\u5371', '\u6637', '\u7fc1',
'\u631d', '\u4e4c', '\u5915', '\u8672', '\u4eda', '\u4e61',
'\u7071', '\u4e9b', '\u5fc3', '\u661f', '\u51f6', '\u4f11',
'\u5401', '\u5405', '\u524a', '\u5743', '\u4e2b', '\u6079',
'\u592e', '\u5e7a', '\u503b', '\u4e00', '\u56d9', '\u5e94',
'\u54df', '\u4f63', '\u4f18', '\u625c', '\u56e6', '\u66f0',
'\u6655', '\u7b60', '\u7b7c', '\u5e00', '\u707d', '\u5142',
'\u5328', '\u50ae', '\u5219', '\u8d3c', '\u600e', '\u5897',
'\u624e', '\u635a', '\u6cbe', '\u5f20', '\u957f', '\u9577',
'\u4f4b', '\u8707', '\u8d1e', '\u4e89', '\u4e4b', '\u5cd9',
'\u5ea2', '\u4e2d', '\u5dde', '\u6731', '\u6293', '\u62fd',
'\u4e13', '\u5986', '\u96b9', '\u5b92', '\u5353', '\u4e72',
'\u5b97', '\u90b9', '\u79df', '\u94bb', '\u539c', '\u5c0a',
'\u6628', '\u5159', '\u9fc3', '\u9fc4',};
/**
* Pinyin array.
* <p/>
* Each pinyin is corresponding to unihans of same
* offset in the unihans array.
*/
public static final byte[][] PINYINS = {
{65, 0, 0, 0, 0, 0}, {65, 73, 0, 0, 0, 0},
{65, 78, 0, 0, 0, 0}, {65, 78, 71, 0, 0, 0},
{65, 79, 0, 0, 0, 0}, {66, 65, 0, 0, 0, 0},
{66, 65, 73, 0, 0, 0}, {66, 65, 78, 0, 0, 0},
{66, 65, 78, 71, 0, 0}, {66, 65, 79, 0, 0, 0},
{66, 69, 73, 0, 0, 0}, {66, 69, 78, 0, 0, 0},
{66, 69, 78, 71, 0, 0}, {66, 73, 0, 0, 0, 0},
{66, 73, 65, 78, 0, 0}, {66, 73, 65, 79, 0, 0},
{66, 73, 69, 0, 0, 0}, {66, 73, 78, 0, 0, 0},
{66, 73, 78, 71, 0, 0}, {66, 79, 0, 0, 0, 0},
{66, 85, 0, 0, 0, 0}, {67, 65, 0, 0, 0, 0},
{67, 65, 73, 0, 0, 0}, {67, 65, 78, 0, 0, 0},
{67, 65, 78, 71, 0, 0}, {67, 65, 79, 0, 0, 0},
{67, 69, 0, 0, 0, 0}, {67, 69, 78, 0, 0, 0},
{67, 69, 78, 71, 0, 0}, {90, 69, 78, 71, 0, 0},
{67, 69, 78, 71, 0, 0}, {67, 72, 65, 0, 0, 0},
{67, 72, 65, 73, 0, 0}, {67, 72, 65, 78, 0, 0},
{67, 72, 65, 78, 71, 0}, {67, 72, 65, 79, 0, 0},
{67, 72, 69, 0, 0, 0}, {67, 72, 69, 78, 0, 0},
{83, 72, 69, 78, 0, 0}, {67, 72, 69, 78, 0, 0},
{67, 72, 69, 78, 71, 0}, {67, 72, 73, 0, 0, 0},
{67, 72, 79, 78, 71, 0}, {67, 72, 79, 85, 0, 0},
{67, 72, 85, 0, 0, 0}, {67, 72, 85, 65, 0, 0},
{67, 72, 85, 65, 73, 0}, {67, 72, 85, 65, 78, 0},
{67, 72, 85, 65, 78, 71}, {67, 72, 85, 73, 0, 0},
{67, 72, 85, 78, 0, 0}, {67, 72, 85, 79, 0, 0},
{67, 73, 0, 0, 0, 0}, {67, 79, 78, 71, 0, 0},
{67, 79, 85, 0, 0, 0}, {67, 85, 0, 0, 0, 0},
{67, 85, 65, 78, 0, 0}, {67, 85, 73, 0, 0, 0},
{67, 85, 78, 0, 0, 0}, {67, 85, 79, 0, 0, 0},
{68, 65, 0, 0, 0, 0}, {68, 65, 73, 0, 0, 0},
{68, 65, 78, 0, 0, 0}, {68, 65, 78, 71, 0, 0},
{68, 65, 79, 0, 0, 0}, {68, 69, 0, 0, 0, 0},
{68, 69, 78, 0, 0, 0}, {68, 69, 78, 71, 0, 0},
{68, 73, 0, 0, 0, 0}, {68, 73, 65, 0, 0, 0},
{68, 73, 65, 78, 0, 0}, {68, 73, 65, 79, 0, 0},
{68, 73, 69, 0, 0, 0}, {68, 73, 78, 71, 0, 0},
{68, 73, 85, 0, 0, 0}, {68, 79, 78, 71, 0, 0},
{68, 79, 85, 0, 0, 0}, {68, 85, 0, 0, 0, 0},
{68, 85, 65, 78, 0, 0}, {68, 85, 73, 0, 0, 0},
{68, 85, 78, 0, 0, 0}, {68, 85, 79, 0, 0, 0},
{69, 0, 0, 0, 0, 0}, {69, 73, 0, 0, 0, 0},
{69, 78, 0, 0, 0, 0}, {69, 78, 71, 0, 0, 0},
{69, 82, 0, 0, 0, 0}, {70, 65, 0, 0, 0, 0},
{70, 65, 78, 0, 0, 0}, {70, 65, 78, 71, 0, 0},
{70, 69, 73, 0, 0, 0}, {70, 69, 78, 0, 0, 0},
{70, 69, 78, 71, 0, 0}, {70, 73, 65, 79, 0, 0},
{70, 79, 0, 0, 0, 0}, {70, 79, 85, 0, 0, 0},
{70, 85, 0, 0, 0, 0}, {71, 65, 0, 0, 0, 0},
{71, 65, 73, 0, 0, 0}, {71, 65, 78, 0, 0, 0},
{71, 65, 78, 71, 0, 0}, {71, 65, 79, 0, 0, 0},
{71, 69, 0, 0, 0, 0}, {71, 69, 73, 0, 0, 0},
{71, 69, 78, 0, 0, 0}, {71, 69, 78, 71, 0, 0},
{71, 79, 78, 71, 0, 0}, {71, 79, 85, 0, 0, 0},
{71, 85, 0, 0, 0, 0}, {71, 85, 65, 0, 0, 0},
{71, 85, 65, 73, 0, 0}, {71, 85, 65, 78, 0, 0},
{71, 85, 65, 78, 71, 0}, {71, 85, 73, 0, 0, 0},
{71, 85, 78, 0, 0, 0}, {71, 85, 79, 0, 0, 0},
{72, 65, 0, 0, 0, 0}, {72, 65, 73, 0, 0, 0},
{72, 65, 78, 0, 0, 0}, {72, 65, 78, 71, 0, 0},
{72, 65, 79, 0, 0, 0}, {72, 69, 0, 0, 0, 0},
{72, 69, 73, 0, 0, 0}, {72, 69, 78, 0, 0, 0},
{72, 69, 78, 71, 0, 0}, {72, 77, 0, 0, 0, 0},
{72, 79, 78, 71, 0, 0}, {72, 79, 85, 0, 0, 0},
{72, 85, 0, 0, 0, 0}, {72, 85, 65, 0, 0, 0},
{72, 85, 65, 73, 0, 0}, {72, 85, 65, 78, 0, 0},
{72, 85, 65, 78, 71, 0}, {72, 85, 73, 0, 0, 0},
{72, 85, 78, 0, 0, 0}, {72, 85, 79, 0, 0, 0},
{74, 73, 0, 0, 0, 0}, {74, 73, 65, 0, 0, 0},
{74, 73, 65, 78, 0, 0}, {74, 73, 65, 78, 71, 0},
{74, 73, 65, 79, 0, 0}, {74, 73, 69, 0, 0, 0},
{74, 73, 78, 0, 0, 0}, {74, 73, 78, 71, 0, 0},
{74, 73, 79, 78, 71, 0}, {74, 73, 85, 0, 0, 0},
{74, 85, 0, 0, 0, 0}, {74, 85, 65, 78, 0, 0},
{74, 85, 69, 0, 0, 0}, {74, 85, 78, 0, 0, 0},
{75, 65, 0, 0, 0, 0}, {75, 65, 73, 0, 0, 0},
{75, 65, 78, 0, 0, 0}, {75, 65, 78, 71, 0, 0},
{75, 65, 79, 0, 0, 0}, {75, 69, 0, 0, 0, 0},
{75, 69, 78, 0, 0, 0}, {75, 69, 78, 71, 0, 0},
{75, 79, 78, 71, 0, 0}, {75, 79, 85, 0, 0, 0},
{75, 85, 0, 0, 0, 0}, {75, 85, 65, 0, 0, 0},
{75, 85, 65, 73, 0, 0}, {75, 85, 65, 78, 0, 0},
{75, 85, 65, 78, 71, 0}, {75, 85, 73, 0, 0, 0},
{75, 85, 78, 0, 0, 0}, {75, 85, 79, 0, 0, 0},
{76, 65, 0, 0, 0, 0}, {76, 65, 73, 0, 0, 0},
{76, 65, 78, 0, 0, 0}, {76, 65, 78, 71, 0, 0},
{76, 65, 79, 0, 0, 0}, {76, 69, 0, 0, 0, 0},
{76, 69, 73, 0, 0, 0}, {76, 69, 78, 71, 0, 0},
{76, 73, 0, 0, 0, 0}, {76, 73, 65, 0, 0, 0},
{76, 73, 65, 78, 0, 0}, {76, 73, 65, 78, 71, 0},
{76, 73, 65, 79, 0, 0}, {76, 73, 69, 0, 0, 0},
{76, 73, 78, 0, 0, 0}, {76, 73, 78, 71, 0, 0},
{76, 73, 85, 0, 0, 0}, {76, 79, 0, 0, 0, 0},
{76, 79, 78, 71, 0, 0}, {76, 79, 85, 0, 0, 0},
{76, 85, 0, 0, 0, 0}, {76, 85, 65, 78, 0, 0},
{76, 85, 69, 0, 0, 0}, {76, 85, 78, 0, 0, 0},
{76, 85, 79, 0, 0, 0}, {77, 0, 0, 0, 0, 0},
{77, 65, 0, 0, 0, 0}, {77, 65, 73, 0, 0, 0},
{77, 65, 78, 0, 0, 0}, {77, 65, 78, 71, 0, 0},
{77, 65, 79, 0, 0, 0}, {77, 69, 0, 0, 0, 0},
{77, 69, 73, 0, 0, 0}, {77, 69, 78, 0, 0, 0},
{77, 69, 78, 71, 0, 0}, {77, 73, 0, 0, 0, 0},
{77, 73, 65, 78, 0, 0}, {77, 73, 65, 79, 0, 0},
{77, 73, 69, 0, 0, 0}, {77, 73, 78, 0, 0, 0},
{77, 73, 78, 71, 0, 0}, {77, 73, 85, 0, 0, 0},
{77, 79, 0, 0, 0, 0}, {77, 79, 85, 0, 0, 0},
{77, 85, 0, 0, 0, 0}, {78, 0, 0, 0, 0, 0},
{78, 65, 0, 0, 0, 0}, {78, 65, 73, 0, 0, 0},
{78, 65, 78, 0, 0, 0}, {78, 65, 78, 71, 0, 0},
{78, 65, 79, 0, 0, 0}, {78, 69, 0, 0, 0, 0},
{78, 69, 73, 0, 0, 0}, {78, 69, 78, 0, 0, 0},
{78, 69, 78, 71, 0, 0}, {78, 73, 0, 0, 0, 0},
{78, 73, 65, 78, 0, 0}, {78, 73, 65, 78, 71, 0},
{78, 73, 65, 79, 0, 0}, {78, 73, 69, 0, 0, 0},
{78, 73, 78, 0, 0, 0}, {78, 73, 78, 71, 0, 0},
{78, 73, 85, 0, 0, 0}, {78, 79, 78, 71, 0, 0},
{78, 79, 85, 0, 0, 0}, {78, 85, 0, 0, 0, 0},
{78, 85, 65, 78, 0, 0}, {78, 85, 69, 0, 0, 0},
{78, 85, 78, 0, 0, 0}, {78, 85, 79, 0, 0, 0},
{79, 0, 0, 0, 0, 0}, {79, 85, 0, 0, 0, 0},
{80, 65, 0, 0, 0, 0}, {80, 65, 73, 0, 0, 0},
{80, 65, 78, 0, 0, 0}, {80, 65, 78, 71, 0, 0},
{80, 65, 79, 0, 0, 0}, {80, 69, 73, 0, 0, 0},
{80, 69, 78, 0, 0, 0}, {80, 69, 78, 71, 0, 0},
{80, 73, 0, 0, 0, 0}, {80, 73, 65, 78, 0, 0},
{80, 73, 65, 79, 0, 0}, {80, 73, 69, 0, 0, 0},
{80, 73, 78, 0, 0, 0}, {80, 73, 78, 71, 0, 0},
{80, 79, 0, 0, 0, 0}, {80, 79, 85, 0, 0, 0},
{80, 85, 0, 0, 0, 0}, {81, 73, 0, 0, 0, 0},
{81, 73, 65, 0, 0, 0}, {81, 73, 65, 78, 0, 0},
{81, 73, 65, 78, 71, 0}, {81, 73, 65, 79, 0, 0},
{81, 73, 69, 0, 0, 0}, {81, 73, 78, 0, 0, 0},
{81, 73, 78, 71, 0, 0}, {81, 73, 79, 78, 71, 0},
{81, 73, 85, 0, 0, 0}, {81, 85, 0, 0, 0, 0},
{81, 85, 65, 78, 0, 0}, {81, 85, 69, 0, 0, 0},
{81, 85, 78, 0, 0, 0}, {82, 65, 78, 0, 0, 0},
{82, 65, 78, 71, 0, 0}, {82, 65, 79, 0, 0, 0},
{82, 69, 0, 0, 0, 0}, {82, 69, 78, 0, 0, 0},
{82, 69, 78, 71, 0, 0}, {82, 73, 0, 0, 0, 0},
{82, 79, 78, 71, 0, 0}, {82, 79, 85, 0, 0, 0},
{82, 85, 0, 0, 0, 0}, {82, 85, 65, 0, 0, 0},
{82, 85, 65, 78, 0, 0}, {82, 85, 73, 0, 0, 0},
{82, 85, 78, 0, 0, 0}, {82, 85, 79, 0, 0, 0},
{83, 65, 0, 0, 0, 0}, {83, 65, 73, 0, 0, 0},
{83, 65, 78, 0, 0, 0}, {83, 65, 78, 71, 0, 0},
{83, 65, 79, 0, 0, 0}, {83, 69, 0, 0, 0, 0},
{83, 69, 78, 0, 0, 0}, {83, 69, 78, 71, 0, 0},
{83, 72, 65, 0, 0, 0}, {83, 72, 65, 73, 0, 0},
{83, 72, 65, 78, 0, 0}, {83, 72, 65, 78, 71, 0},
{83, 72, 65, 79, 0, 0}, {83, 72, 69, 0, 0, 0},
{83, 72, 69, 78, 0, 0}, {88, 73, 78, 0, 0, 0},
{83, 72, 69, 78, 0, 0}, {83, 72, 69, 78, 71, 0},
{83, 72, 73, 0, 0, 0}, {83, 72, 79, 85, 0, 0},
{83, 72, 85, 0, 0, 0}, {83, 72, 85, 65, 0, 0},
{83, 72, 85, 65, 73, 0}, {83, 72, 85, 65, 78, 0},
{83, 72, 85, 65, 78, 71}, {83, 72, 85, 73, 0, 0},
{83, 72, 85, 78, 0, 0}, {83, 72, 85, 79, 0, 0},
{83, 73, 0, 0, 0, 0}, {83, 79, 78, 71, 0, 0},
{83, 79, 85, 0, 0, 0}, {83, 85, 0, 0, 0, 0},
{83, 85, 65, 78, 0, 0}, {83, 85, 73, 0, 0, 0},
{83, 85, 78, 0, 0, 0}, {83, 85, 79, 0, 0, 0},
{84, 65, 0, 0, 0, 0}, {84, 65, 73, 0, 0, 0},
{84, 65, 78, 0, 0, 0}, {84, 65, 78, 71, 0, 0},
{84, 65, 79, 0, 0, 0}, {84, 69, 0, 0, 0, 0},
{84, 69, 78, 71, 0, 0}, {84, 73, 0, 0, 0, 0},
{84, 73, 65, 78, 0, 0}, {84, 73, 65, 79, 0, 0},
{84, 73, 69, 0, 0, 0}, {84, 73, 78, 71, 0, 0},
{84, 79, 78, 71, 0, 0}, {84, 79, 85, 0, 0, 0},
{84, 85, 0, 0, 0, 0}, {84, 85, 65, 78, 0, 0},
{84, 85, 73, 0, 0, 0}, {84, 85, 78, 0, 0, 0},
{84, 85, 79, 0, 0, 0}, {87, 65, 0, 0, 0, 0},
{87, 65, 73, 0, 0, 0}, {87, 65, 78, 0, 0, 0},
{87, 65, 78, 71, 0, 0}, {87, 69, 73, 0, 0, 0},
{87, 69, 78, 0, 0, 0}, {87, 69, 78, 71, 0, 0},
{87, 79, 0, 0, 0, 0}, {87, 85, 0, 0, 0, 0},
{88, 73, 0, 0, 0, 0}, {88, 73, 65, 0, 0, 0},
{88, 73, 65, 78, 0, 0}, {88, 73, 65, 78, 71, 0},
{88, 73, 65, 79, 0, 0}, {88, 73, 69, 0, 0, 0},
{88, 73, 78, 0, 0, 0}, {88, 73, 78, 71, 0, 0},
{88, 73, 79, 78, 71, 0}, {88, 73, 85, 0, 0, 0},
{88, 85, 0, 0, 0, 0}, {88, 85, 65, 78, 0, 0},
{88, 85, 69, 0, 0, 0}, {88, 85, 78, 0, 0, 0},
{89, 65, 0, 0, 0, 0}, {89, 65, 78, 0, 0, 0},
{89, 65, 78, 71, 0, 0}, {89, 65, 79, 0, 0, 0},
{89, 69, 0, 0, 0, 0}, {89, 73, 0, 0, 0, 0},
{89, 73, 78, 0, 0, 0}, {89, 73, 78, 71, 0, 0},
{89, 79, 0, 0, 0, 0}, {89, 79, 78, 71, 0, 0},
{89, 79, 85, 0, 0, 0}, {89, 85, 0, 0, 0, 0},
{89, 85, 65, 78, 0, 0}, {89, 85, 69, 0, 0, 0},
{89, 85, 78, 0, 0, 0}, {74, 85, 78, 0, 0, 0},
{89, 85, 78, 0, 0, 0}, {90, 65, 0, 0, 0, 0},
{90, 65, 73, 0, 0, 0}, {90, 65, 78, 0, 0, 0},
{90, 65, 78, 71, 0, 0}, {90, 65, 79, 0, 0, 0},
{90, 69, 0, 0, 0, 0}, {90, 69, 73, 0, 0, 0},
{90, 69, 78, 0, 0, 0}, {90, 69, 78, 71, 0, 0},
{90, 72, 65, 0, 0, 0}, {90, 72, 65, 73, 0, 0},
{90, 72, 65, 78, 0, 0}, {90, 72, 65, 78, 71, 0},
{67, 72, 65, 78, 71, 0}, {90, 72, 65, 78, 71, 0},
{90, 72, 65, 79, 0, 0}, {90, 72, 69, 0, 0, 0},
{90, 72, 69, 78, 0, 0}, {90, 72, 69, 78, 71, 0},
{90, 72, 73, 0, 0, 0}, {83, 72, 73, 0, 0, 0},
{90, 72, 73, 0, 0, 0}, {90, 72, 79, 78, 71, 0},
{90, 72, 79, 85, 0, 0}, {90, 72, 85, 0, 0, 0},
{90, 72, 85, 65, 0, 0}, {90, 72, 85, 65, 73, 0},
{90, 72, 85, 65, 78, 0}, {90, 72, 85, 65, 78, 71},
{90, 72, 85, 73, 0, 0}, {90, 72, 85, 78, 0, 0},
{90, 72, 85, 79, 0, 0}, {90, 73, 0, 0, 0, 0},
{90, 79, 78, 71, 0, 0}, {90, 79, 85, 0, 0, 0},
{90, 85, 0, 0, 0, 0}, {90, 85, 65, 78, 0, 0},
{90, 85, 73, 0, 0, 0}, {90, 85, 78, 0, 0, 0},
{90, 85, 79, 0, 0, 0}, {0, 0, 0, 0, 0, 0},
{83, 72, 65, 78, 0, 0}, {0, 0, 0, 0, 0, 0},};
/**
* First and last Chinese character with known Pinyin according to zh collation
*/
private static final String FIRST_PINYIN_UNIHAN = "\u963F";
private static final String LAST_PINYIN_UNIHAN = "\u9FFF";
private static final Collator COLLATOR = Collator.getInstance(Locale.CHINA);
private static HanziToPinyin sInstance;
private final boolean mHasChinaCollator;
public static class Token {
/**
* Separator between target string for each source char
*/
public static final String SEPARATOR = " ";
public static final int LATIN = 1;
public static final int PINYIN = 2;
public static final int UNKNOWN = 3;
public Token() {
}
public Token(int type, String source, String target) {
this.type = type;
this.source = source;
this.target = target;
}
/**
* Type of this token, ASCII, PINYIN or UNKNOWN.
*/
public int type;
/**
* Original string before translation.
*/
public String source;
/**
* Translated string of source. For Han, target is corresponding Pinyin. Otherwise target is
* original string in source.
*/
public String target;
}
protected HanziToPinyin(boolean hasChinaCollator) {
mHasChinaCollator = hasChinaCollator;
}
public static HanziToPinyin getInstance() {
synchronized (HanziToPinyin.class) {
if (sInstance != null) {
return sInstance;
}
// Check if zh_CN collation data is available
// final Locale locale[] = Collator.getAvailableLocales();
// for (int i = 0; i < locale.length; i++) {
// if (locale[i].equals(Locale.CHINA)) {
// // Do self validation just once.
// if (DEBUG) {
// Log.d(TAG, "Self validation. Result: " + doSelfValidation());
// }
// sInstance = new HanziToPinyin(true);
// return sInstance;
// }
// }
// Log.w(TAG, "There is no Chinese collator, HanziToPinyin is disabled");
// sInstance = new HanziToPinyin(false);
sInstance = new HanziToPinyin(true);
return sInstance;
}
}
/**
* Validate if our internal table has some wrong value.
*
* @return true when the table looks correct.
*/
private static boolean doSelfValidation() {
char lastChar = UNIHANS[0];
String lastString = Character.toString(lastChar);
for (char c : UNIHANS) {
if (lastChar == c) {
continue;
}
final String curString = Character.toString(c);
int cmp = COLLATOR.compare(lastString, curString);
if (cmp >= 0) {
Log.e(TAG, "Internal error in Unihan table. " + "The last string \"" + lastString
+ "\" is greater than current string \"" + curString + "\".");
return false;
}
lastString = curString;
}
return true;
}
private Token getToken(char character) {
Token token = new Token();
final String letter = Character.toString(character);
token.source = letter;
int offset = -1;
int cmp;
if (character < 256) {
token.type = Token.LATIN;
token.target = letter;
return token;
} else {
cmp = COLLATOR.compare(letter, FIRST_PINYIN_UNIHAN);
if (cmp < 0) {
token.type = Token.UNKNOWN;
token.target = letter;
return token;
} else if (cmp == 0) {
token.type = Token.PINYIN;
offset = 0;
} else {
cmp = COLLATOR.compare(letter, LAST_PINYIN_UNIHAN);
if (cmp > 0) {
token.type = Token.UNKNOWN;
token.target = letter;
return token;
} else if (cmp == 0) {
token.type = Token.PINYIN;
offset = UNIHANS.length - 1;
}
}
}
token.type = Token.PINYIN;
if (offset < 0) {
int begin = 0;
int end = UNIHANS.length - 1;
while (begin <= end) {
offset = (begin + end) / 2;
final String unihan = Character.toString(UNIHANS[offset]);
cmp = COLLATOR.compare(letter, unihan);
if (cmp == 0) {
break;
} else if (cmp > 0) {
begin = offset + 1;
} else {
end = offset - 1;
}
}
}
if (cmp < 0) {
offset--;
}
StringBuilder pinyin = new StringBuilder();
for (int j = 0; j < PINYINS[offset].length && PINYINS[offset][j] != 0; j++) {
pinyin.append((char) PINYINS[offset][j]);
}
token.target = pinyin.toString();
if (TextUtils.isEmpty(token.target)) {
token.type = Token.UNKNOWN;
token.target = token.source;
}
return token;
}
/**
* Convert the input to a array of tokens. The sequence of ASCII or Unknown characters without
* space will be put into a Token, One Hanzi character which has pinyin will be treated as a
* Token. If these is no China collator, the empty token array is returned.
*/
public ArrayList<Token> get(final String input) {
ArrayList<Token> tokens = new ArrayList<Token>();
if (!mHasChinaCollator || TextUtils.isEmpty(input)) {
// return empty tokens.
return tokens;
}
final int inputLength = input.length();
final StringBuilder sb = new StringBuilder();
int tokenType = Token.LATIN;
// Go through the input, create a new token when
// a. Token type changed
// b. Get the Pinyin of current charater.
// c. current character is space.
for (int i = 0; i < inputLength; i++) {
final char character = input.charAt(i);
if (character == ' ') {
if (sb.length() > 0) {
addToken(sb, tokens, tokenType);
}
} else if (character < 256) {
if (tokenType != Token.LATIN && sb.length() > 0) {
addToken(sb, tokens, tokenType);
}
tokenType = Token.LATIN;
sb.append(character);
} else {
Token t = getToken(character);
if (t.type == Token.PINYIN) {
if (sb.length() > 0) {
addToken(sb, tokens, tokenType);
}
tokens.add(t);
tokenType = Token.PINYIN;
} else {
if (tokenType != t.type && sb.length() > 0) {
addToken(sb, tokens, tokenType);
}
tokenType = t.type;
sb.append(character);
}
}
}
if (sb.length() > 0) {
addToken(sb, tokens, tokenType);
}
return tokens;
}
private void addToken(
final StringBuilder sb, final ArrayList<Token> tokens, final int tokenType) {
String str = sb.toString();
tokens.add(new Token(tokenType, str, str));
sb.setLength(0);
}
public static String getSortKey(String input) {
ArrayList<Token> tokens = HanziToPinyin.getInstance().get(input);
StringBuilder sb = new StringBuilder();
if (tokens != null && tokens.size() > 0) {
for (Token token : tokens) {
if (Token.PINYIN == token.type) {
sb.append(token.target.toLowerCase());
} else {
sb.append(token.source);
}
}
}
return sb.toString();
}
}
排序实现:
public static class MyComparator implements Comparator<String> {
@Override
public int compare(String lhs, String rhs) {
if (lhs == null) {
return -1;
}
if (rhs == null) {
return 1;
}
if (lhs == "") {
return -1;
}
if (rhs == "") {
return 1;
}
String lhsSortKey = HanziToPinyin.getSortKey(lhs).toLowerCase();
String rhsSortKey = HanziToPinyin.getSortKey(rhs).toLowerCase();
int lhsLength = lhsSortKey.length();
int rhsLength = rhsSortKey.length();
for (int i = 0; i < lhsLength && i < rhsLength; i++) {
char lhsChar = lhsSortKey.charAt(i);
char rhsChar = rhsSortKey.charAt(i);
if (lhsChar == rhsChar) {
} else if (!isNumber(lhsChar) && !isLetter(lhsChar)) {
if (isLetter(rhsChar)) {
return -1;
} else if (isNumber(rhsChar)) {
return -1;
} else {
return lhsChar > rhsChar ? 1 : -1;
}
} else if (isNumber(lhsChar)) {
if (isLetter(rhsChar)) {
return -1;
} else if (isNumber(rhsChar)) {
return lhsChar > rhsChar ? 1 : -1;
} else {
return 1;
}
} else {
if (isLetter(rhsChar)) {
return lhsChar > rhsChar ? 1 : -1;
} else if (isNumber(rhsChar)) {
return 1;
} else {
return 1;
}
}
}
if (lhsLength > rhsLength) {
return 1;
} else if (lhsLength == rhsLength) {
return 0;
} else {
return -1;
}
}
}
2.listview实现
首先是adapter的实现,使用自定义adapter,主要是定义了head以及具体的数据
import android.content.Context;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import java.util.ArrayList;
import java.util.List;
/**
* Created by whuthmon 2015/9/16.
*/
public abstract class PartitionAdapter<T> extends BaseAdapter {
private final Context mContext;
private int mCount;
private boolean mCacheValid = true;
private List<Partition> mPartitions;
public PartitionAdapter(Context context) {
mContext = context;
mPartitions = new ArrayList<Partition>();
}
public static class Partition<T> {
boolean showIfEmpty;
boolean hasHeader;
int count;
List<T> infos;
Object headInfo;
public Partition(boolean showIfEmpty, boolean hasHeader) {
this.showIfEmpty = showIfEmpty;
this.hasHeader = hasHeader;
}
/**
* True if the directory should be shown even if no contacts are found.
*/
public boolean getShowIfEmpty() {
return showIfEmpty;
}
public boolean getHasHeader() {
return hasHeader;
}
public boolean isEmpty() {
return count == 0;
}
}
protected void ensureCacheValid() {
if (mCacheValid) {
return;
}
mCount = 0;
for (Partition partition : mPartitions) {
List<T> infos = partition.infos;
int count = infos != null ? infos.size() : 0;
if (partition.hasHeader) {
if (count != 0 || partition.showIfEmpty) {
count++;
}
}
partition.count = count;
mCount += count;
}
mCacheValid = true;
}
protected void invalidate() {
mCacheValid = false;
}
public int getPartitionCount() {
return mPartitions.size();
}
/**
* Returns true if the specified partition was configured to have a header.
*/
public boolean hasHeader(int partition) {
return mPartitions.get(partition).hasHeader;
}
/**
* Returns the total number of list items in all partitions.
*/
@Override
public int getCount() {
ensureCacheValid();
return mCount;
}
public List<T> getPartitionInfos(int partition) {
return mPartitions.get(partition).infos;
}
public Object getPartitionHeadInfo(int partition) {
return mPartitions.get(partition).headInfo;
}
public void changeInfos(int partition, List<T> infos) {
}
public void addPartition(boolean showIfEmpty, boolean hasHeader) {
addPartition(new Partition(showIfEmpty, hasHeader));
}
public void addPartition(Partition partition) {
mPartitions.add(partition);
invalidate();
notifyDataSetChanged();
}
public void addPartition(int index, Partition partition) {
mPartitions.add(index, partition);
invalidate();
notifyDataSetChanged();
}
public void removePartition(int partitionIndex) {
mPartitions.remove(partitionIndex);
invalidate();
notifyDataSetChanged();
}
public void removePartition(Partition partition) {
mPartitions.remove(partition);
invalidate();
notifyDataSetChanged();
}
public int getIndexByHead(Object headInfo) {
int totalCount = 0;
for (Partition partition : mPartitions) {
if(headInfo != null && headInfo.equals(partition.headInfo)) {
return totalCount;
}
List<T> infos = partition.infos;
int count = infos != null ? infos.size() : 0;
if (partition.hasHeader) {
if (count != 0 || partition.showIfEmpty) {
count++;
}
}
partition.count = count;
totalCount += count;
}
return -1;
}
public int getIndexByPartitionIndex(int partitionIndex) {
int index = -1;
int size = mPartitions.size();
for(int i = 0; i <= partitionIndex && i < size; i++) {
if(i < partitionIndex) {
Partition partition = mPartitions.get(i);
List<T> infos = partition.infos;
int count = infos != null ? infos.size() : 0;
if (partition.hasHeader) {
if (count != 0 || partition.showIfEmpty) {
count++;
}
}
index += count;
} else {
index ++;
}
}
return index;
}
public int getPartitionIndexByIndex(int index) {
int partitionIndex = -1;
if(index < 0 || index >= mCount) {
return partitionIndex;
}
int size = mPartitions.size();
for(int i = 0; i < size; i++) {
Partition partition = mPartitions.get(i);
List<T> infos = partition.infos;
int count = infos != null ? infos.size() : 0;
if (partition.hasHeader) {
if (count != 0 || partition.showIfEmpty) {
count++;
}
}
index -= count;
if(index < 0) {
partitionIndex = i;
break;
}
}
return partitionIndex;
}
public void clearPartitions() {
for (Partition partition : mPartitions) {
if(partition.infos != null) {
partition.infos.clear();
}
}
mPartitions.clear();
invalidate();
notifyDataSetChanged();
}
@Override
public Object getItem(int position) {
ensureCacheValid();
int start = 0;
for (Partition mPartition : mPartitions) {
int end = start + mPartition.count;
if (position >= start && position < end) {
int offset = position - start;
if (mPartition.hasHeader) {
offset--;
}
if (offset == -1) {
return mPartition.headInfo;
}
List<T> infos = mPartition.infos;
if (infos == null || offset < 0 || offset >= infos.size()) {
return null;
}
return infos.get(offset);
}
start = end;
}
return null;
}
@Override
public long getItemId(int position) {
ensureCacheValid();
return position;
}
/**
* Returns false if any partition has a header.
*/
@Override
public boolean areAllItemsEnabled() {
for (Partition mPartition : mPartitions) {
if (mPartition.hasHeader) {
return false;
}
}
return true;
}
/**
* Returns true for all items except headers.
*/
@Override
public boolean isEnabled(int position) {
ensureCacheValid();
int start = 0;
for (int i = 0, n = mPartitions.size(); i < n; i++) {
int end = start + mPartitions.get(i).count;
if (position >= start && position < end) {
int offset = position - start;
if (mPartitions.get(i).hasHeader && offset == 0) {
return false;
} else {
return isEnabled(i, offset);
}
}
start = end;
}
return false;
}
/**
* Returns true if the item at the specified offset of the specified
* partition is selectable and clickable.
*/
protected boolean isEnabled(int partition, int position) {
return true;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ensureCacheValid();
int start = 0;
for (int i = 0, n = mPartitions.size(); i < n; i++) {
int end = start + mPartitions.get(i).count;
if (position >= start && position < end) {
int offset = position - start;
if (mPartitions.get(i).hasHeader && mPartitions.get(i).count > 0) {
offset--;
}
return getView(i, mPartitions.get(i).infos, mPartitions.get(i).headInfo, offset, convertView, parent, offset == -1);
}
start = end;
}
throw new ArrayIndexOutOfBoundsException(position);
}
@Override
public void notifyDataSetChanged() {
super.notifyDataSetChanged();
}
/**
* Returns an item view for the specified partition, creating one if needed.
*/
protected View getView(int partition, List<T> infos, Object headInfo, int position, View convertView,
ViewGroup parent, boolean isHead) {
View view;
if (convertView != null) {
view = convertView;
} else {
view = newView(mContext, parent);
}
bindView(view, partition, infos, headInfo, position, isHead);
return view;
}
/**
* Creates an item view for the specified partition and position. Position
* corresponds directly to the current cursor position.
*/
protected abstract View newView(Context context, ViewGroup parent);
/**
* Binds an item view for the specified partition and position. Position
* corresponds directly to the current cursor position.
*/
protected abstract void bindView(View v, int partition, List<T> infos, Object headInfo, int position, boolean isHead);
}
自定义的MyAdapter
static class MyAdapter extends PartitionAdapter<String> {
public MyAdapter(Context context) {
super(context);
}
@Override
protected View newView(Context context, ViewGroup parent) {
return LayoutInflater.from(context).inflate(R.layout.item_my, parent, false);
}
@Override
protected void bindView(View v, int partition, List<String> infos, Object headInfo, int position, boolean isHead) {
v.findViewById(R.id.item_partition_content).setVisibility(isHead ? View.GONE : View.VISIBLE);
v.findViewById(R.id.item_partition_head).setVisibility(isHead ? View.VISIBLE : View.GONE);
if (isHead) {
TextView tv = (TextView) v.findViewById(R.id.tv_head);
tv.setText((String) headInfo);
} else {
TextView tv = (TextView) v.findViewById(R.id.tv_item);
tv.setText(infos.get(position));
}
}
}
3.侧边字母集的实现
仿造源生联系人中的BladeView,精简了很多
import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Typeface;
import android.os.Handler;
import android.util.AttributeSet;
import android.util.Log;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.widget.AbsListView;
import android.widget.PopupWindow;
import android.widget.TextView;
import android.widget.Toast;
import com.whuthm.contacts.R;
public class BladeView extends View implements AbsListView.OnScrollListener {
private static final String TAG = "BladeView";
private static final int DEFAULT_SECTION_COLOR = Color.argb(0xFF, 0xA5, 0xA5, 0xA5);
private static final int DEFAULT_SECTION_CURRENT_COLOR = Color.BLACK;
private static final int DEFAULT_PROMPT_COLOR = Color.WHITE;
private Paint mSectionPaint;
private int mSectionColor;
private Paint mCurrentSectionPaint;
private int mCurrentSelectionColor;
/* Font size of the sections. */
private int mSectionFontSize;
/* The current selected position in full sections. */
private int mCurrentSectionIndex = -1;
private int mTempSectionIndex = -1;
private String[] mFullSections;
private boolean[] mIsAbsentSections;
private int[] mSectionBaselines;
private int mSectionHeight;
private SectionListener mListener;
/* The prompt window and the text view showing in the window. */
private PopupWindow mPromptWindow;
private int mPromptColor;
public BladeView(Context context) {
this(context, null);
}
public BladeView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public BladeView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.BladeView);
final Resources resources = context.getResources();
//Get the rotate animation duration from attributes set.
mSectionFontSize = a.getDimensionPixelSize(R.styleable.BladeView_sectionFontSize,
resources.getDimensionPixelSize(R.dimen.blade_section_font_size));
mSectionColor = a.getColor(R.styleable.BladeView_sectionColor, DEFAULT_SECTION_COLOR);
mCurrentSelectionColor = a.getColor(R.styleable.BladeView_currentSelectionColor,
DEFAULT_SECTION_CURRENT_COLOR);
mPromptColor = a.getColor(R.styleable.BladeView_promptColor,
DEFAULT_PROMPT_COLOR);
mSectionHeight = a.getDimensionPixelSize(R.styleable.BladeView_sectionHeight,
resources.getDimensionPixelSize(R.dimen.blade_section_height));
// Get full sections and replaced sections from attributes set
int fullSectionId = a.getResourceId(R.styleable.BladeView_fullSectionsId, 0);
setSections(fullSectionId);
a.recycle();
init(context);
}
private void init(Context context) {
/* Define the attributes of the paint used to draw sections. */
mSectionPaint = new Paint();
mSectionPaint.setColor(mSectionColor);
mSectionPaint.setTypeface(Typeface.MONOSPACE);
mSectionPaint.setAntiAlias(true);
mSectionPaint.setTextAlign(Paint.Align.CENTER);
mSectionPaint.setTextSize(mSectionFontSize);
mCurrentSectionPaint = new Paint();
mCurrentSectionPaint.setColor(mCurrentSelectionColor);
mCurrentSectionPaint.setTypeface(Typeface.MONOSPACE);
mCurrentSectionPaint.setAntiAlias(true);
mCurrentSectionPaint.setTextAlign(Paint.Align.CENTER);
mCurrentSectionPaint.setTextSize(mSectionFontSize);
}
public void setSectionListener(SectionListener l) {
mListener = l;
}
private void notifySectionSelected(int sectionIndex) {
int fullSectionsLength = getFullSectionsLength();
if (sectionIndex >= 0 && sectionIndex < fullSectionsLength && mIsAbsentSections[sectionIndex] && mListener != null) {
mListener.onSectionSelected(sectionIndex, getActualIndex(sectionIndex), mFullSections[sectionIndex]);
}
}
public int getActualIndex(int sectionIndex) {
int actualIndex = -1;
int fullSectionsLength = getFullSectionsLength();
for (int i = 0; i <= sectionIndex && i < fullSectionsLength; i++) {
if (mIsAbsentSections[i]) {
actualIndex++;
}
}
return actualIndex;
}
public int getSectionIndex(int actualIndex) {
int sectionIndex = -1;
int fullSectionsLength = getFullSectionsLength();
for (int i = 0; i < fullSectionsLength; i++) {
if (mIsAbsentSections[i]) {
actualIndex--;
}
sectionIndex++;
if (actualIndex < 0) {
break;
}
}
return sectionIndex;
}
public void setSections(final int fullSectionId) {
final Resources resources = getContext().getResources();
try {
String[] fullSections = resources.getStringArray(fullSectionId);
setSections(fullSections);
} catch (Resources.NotFoundException e) {
e.printStackTrace();
}
}
public void setSections(final String[] fullSectionArray) {
mFullSections = fullSectionArray;
int fullSectionsLength = getFullSectionsLength();
if (fullSectionsLength > 0) {
mIsAbsentSections = new boolean[fullSectionsLength];
mSectionBaselines = new int[fullSectionsLength];
for (int i = 0; i < fullSectionsLength; i++) {
mIsAbsentSections[i] = true;
}
invalidate();
}
}
public void setAbsentSections(boolean[] isAbsentSections) {
int isAbsentSectionsLength = isAbsentSections != null ? isAbsentSections.length : 0;
int fullSectionsLength = getFullSectionsLength();
if (isAbsentSectionsLength == fullSectionsLength && fullSectionsLength > 0) {
mIsAbsentSections = isAbsentSections;
}
invalidate();
}
public void setAbsentSection(int index, boolean isAbsentSection) {
int fullSectionsLength = getFullSectionsLength();
if (index >= 0 && index < fullSectionsLength) {
mIsAbsentSections[index] = isAbsentSection;
}
}
public void setCurrentSectionIndex(int sectionIndex) {
int fullSectionsLength = getFullSectionsLength();
if (sectionIndex >= 0
&& sectionIndex < fullSectionsLength
&& mIsAbsentSections[sectionIndex]
&& mCurrentSectionIndex != sectionIndex) {
mCurrentSectionIndex = sectionIndex;
invalidate();
}
}
public int getFullSectionsLength() {
return mFullSections != null ? mFullSections.length : 0;
}
public boolean isEmpty() {
return !(mFullSections != null && mFullSections.length >= 0);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
int fullSectionsLength = getFullSectionsLength();
int paddingTop = getPaddingTop();
int paddingBottom = getPaddingBottom();
int contentHeight = fullSectionsLength * mSectionHeight;
int top = paddingTop + (h - paddingTop - paddingBottom - contentHeight) / 2;
for (int i = 0; i < fullSectionsLength; i++) {
mSectionBaselines[i] = top + i * mSectionHeight + mSectionHeight / 2;
}
}
@Override
public void onDraw(Canvas canvas) {
super.onDraw(canvas);
int fullSectionsLen = getFullSectionsLength();
int centerWidth = getMeasuredWidth() >> 1;
for (int i = 0; i < fullSectionsLen; i++) {
if (i == mCurrentSectionIndex && mIsAbsentSections[i]) {
canvas.drawText(mFullSections[i], centerWidth, mSectionBaselines[i], mCurrentSectionPaint);
} else {
canvas.drawText(mFullSections[i], centerWidth, mSectionBaselines[i], mSectionPaint);
}
}
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
int fullSectionsLength = getFullSectionsLength();
if (mSectionHeight <= 0 && fullSectionsLength <= 0) {
return super.onTouchEvent(ev);
}
final int action = ev.getAction();
final int x = (int) ev.getX();
final int y = (int) ev.getY();
int toSection = calcSectionIndex(x, y);
if (action == MotionEvent.ACTION_DOWN) {
mTempSectionIndex = -1;
}
Log.e(TAG, "action " + action + " : toSection = " + toSection + " mCurrentSectionIndex = " + mCurrentSectionIndex);
switch (action) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_MOVE:
final int oldSectionIndex = mTempSectionIndex;
mTempSectionIndex = toSection;
if (toSection != oldSectionIndex) {
notifySectionSelected(toSection);
setCurrentSectionIndex(toSection);
if (toSection >= 0 && toSection < fullSectionsLength && mIsAbsentSections[toSection]) {
showPromptWindow();
}
}
break;
case MotionEvent.ACTION_UP:
mTempSectionIndex = -1;
dismissPromptWindow();
break;
case MotionEvent.ACTION_CANCEL:
default:
mTempSectionIndex = -1;
dismissPromptWindow();
break;
}
return true;
}
private int calcSectionIndex(final float x, final float y) {
int fullSectionsLen = getFullSectionsLength();
int halfSectionHeight = mSectionHeight / 2;
for (int i = 0; i < fullSectionsLen; i++) {
if (y >= mSectionBaselines[i] - halfSectionHeight && y < mSectionBaselines[i] + halfSectionHeight) {
return i;
}
}
return -1;
}
/**
* Start the window exit animation, and then dismiss the pop up window
* after the animation has been played.
*/
private void dismissPromptWindow() {
}
/**
* Create the prompt window and then show it at appropriate position.
*/
private void showPromptWindow() {
//Toast.makeText(getContext(), mFullSections[mCurrentSectionIndex], Toast.LENGTH_LONG).show();
}
/**
* Create the prompt window and set its background to be transparent.
*/
private void createPromptWindow() {
}
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
}
@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
if (view.getAdapter() instanceof PartitionAdapter) {
PartitionAdapter adapter = (PartitionAdapter) view.getAdapter();
int partitionIndex = adapter.getPartitionIndexByIndex(firstVisibleItem);
setCurrentSectionIndex(getSectionIndex(partitionIndex));
}
}
public interface SectionListener {
public void onSectionSelected(int index, int actualIndex, String section);
}
}
4.应用
布局
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:blade="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ListView
android:id="@+id/list"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<com.base.view.BladeView
android:id="@+id/blade"
android:layout_width="30dp"
android:layout_height="match_parent"
android:layout_alignParentRight="true"
blade:fullSectionsId="@array/blade_full_sections" />
</RelativeLayout>
使用
private MyAdapter mAdapter;
private ListView mListView;
private BladeView mBladeView;
private List<String> mNames = new ArrayList<String>();
private SparseArray<List<String>> mNameSparseArray = new SparseArray<List<String>>();
private PartitionAdapter.Partition mPartitions;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main1);
String[] nameArray = getResources().getStringArray(R.array.names);
if (nameArray != null) {
for (String name : nameArray) {
if (!TextUtils.isEmpty(name)) {
char firstChar = HanziToPinyin.getSortKey(name).toUpperCase().charAt(0);
if (isLetter(firstChar)) {
List<String> names = mNameSparseArray.get(firstChar);
if (names == null) {
names = new ArrayList<String>();
mNameSparseArray.put(firstChar, names);
}
names.add(name);
} else {
List<String> names = mNameSparseArray.get('#');
if (names == null) {
names = new ArrayList<String>();
mNameSparseArray.put('#', names);
}
names.add(name);
}
}
}
int length = mNameSparseArray.size();
for (int i = 0; i < length; i++) {
Collections.sort(mNameSparseArray.get(mNameSparseArray.keyAt(i)), new MyComparator());
}
}
mListView = (ListView) findViewById(R.id.list);
mBladeView = (BladeView) findViewById(R.id.blade);
mBladeView.setSectionListener(this);
mAdapter = new MyAdapter(this);
int sectionIndex = 0;
if (mNameSparseArray.get('#') != null) {
PartitionAdapter.Partition partition = new PartitionAdapter.Partition<String>(false, true);
partition.headInfo = String.valueOf('#');
partition.infos = mNameSparseArray.get('#');
mAdapter.addPartition(partition);
mBladeView.setAbsentSection(sectionIndex, true);
}
for (char k = 'A'; k <= 'Z'; k++) {
List<String> names = mNameSparseArray.get(k);
if (names != null) {
PartitionAdapter.Partition partition = new PartitionAdapter.Partition<String>(false, true);
partition.headInfo = String.valueOf(k);
partition.infos = names;
mAdapter.addPartition(partition);
}
sectionIndex++;
mBladeView.setAbsentSection(sectionIndex, names != null);
}
mBladeView.setCurrentSectionIndex(mBladeView.getSectionIndex(0));
mListView.setAdapter(mAdapter);
mListView.setOnScrollListener(mBladeView);
}
@Override
public void onSectionSelected(int index, int actualIndex, String section) {
int targetIndex = mAdapter.getIndexByPartitionIndex(actualIndex);
//mListView.setSelection(targetIndex);
if (Build.VERSION.SDK_INT >= 11) {
mListView.smoothScrollToPositionFromTop(targetIndex, 0, 0);
} else {
mListView.setSelection(targetIndex);
}
}