一:簡言
一個月沒有寫博客了,公司項目比較忙,最近發現公司用到一個知識點,所以抽時間通過博客的形式分享給大家,該知識點,模仿微信的好友列表,通過字母索引進行定位,該功能主要通過幾個知識點實現。下面會一一講解。
二:具體說下思路
- 1 :整體用到的控件,list view(recyclerView原理一樣),自定義view,漢字轉拼音的utils工具類.
- 2:創建一個自定義類繼承view,實現側面滑動字母顯示字母索引數據,說下自定義View,首先自定義類繼承View重寫三個構造方法,重寫
- onMeasure()的方法用於測量View的寬高,onSizeChanged()方法改變當前控件大小的時候調用,onDarw()方法繪製View,
- onTouchEvent()方法用於手勢監聽,自定義接口用於保存點擊了那個字母的值。
- 3:創建一個person的實體bean,實現列表數據的展示,
- 4。通過將漢字通過拼音的形式進行分類,添加一個pinyinj-2.5.0.jar,實現轉換,(稍後代碼會詳細介紹)。
- 5:將數據顯示出來,實現效果;
三:實現效果
- 四:通過代碼的形式介紹步驟:
1)首先實現實體bean(Person)
public class Person {
private String name;
private String pinyin;
public Person(String name){
this.name = name;
this.pinyin = PinYinUtils.getPinYin(name);
}
public String getPinyin() {
return pinyin;
}
public void setPinyin(String pinyin) {
this.pinyin = pinyin;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", pinyin='" + pinyin + '\'' +
'}';
}
}
該類沒有講解的必要,直接刀幣下一個類:
2)拼音轉換工具類(PinYinUtils)
首先添加這個jar包(稍後分享)
然後創建該類(代碼如下)
public class PinYinUtils {
/**
* 得到指定漢字的拼音
* 注意:不應該被頻繁調用,它消耗一定內存
* @param hanzi
* @return
*/
public static String getPinYin(String hanzi){
String pinyin = "";
HanyuPinyinOutputFormat format = new HanyuPinyinOutputFormat();//控制轉換是否大小寫,是否帶音標
format.setCaseType(HanyuPinyinCaseType.UPPERCASE);//大寫
format.setToneType(HanyuPinyinToneType.WITHOUT_TONE);
//由於不能直接對多個漢字轉換,只能對單個漢字轉換
char[] arr = hanzi.toCharArray();
for (int i = 0; i < arr.length; i++) {
if(Character.isWhitespace(arr[i]))continue;//如果是空格,則不處理,進行下次遍歷
//漢字是2個字節存儲,肯定大於127,所以大於127就可以當爲漢字轉換
if(arr[i]>127){
try {
//由於多音字的存在,單 dan shan
String[] pinyinArr = PinyinHelper.toHanyuPinyinStringArray(arr[i], format);
if(pinyinArr!=null){
pinyin += pinyinArr[0];
}else {
pinyin += arr[i];
}
} catch (BadHanyuPinyinOutputFormatCombination e) {
e.printStackTrace();
//不是正確的漢字
pinyin += arr[i];
}
}else {
//不是漢字,
pinyin += arr[i];
}
}
return pinyin;
}
}
3)重點來了,主要實現的功能,自定義view(IndexView)
/**
* Created by wk先森
* 快速索引
* 繪製快速索引的字母
* 1.二十六個字母 放入集合中
* 2.在onMeaus中計算每條的寬和高 itemHeight itemWidth wordHeight wordX wordY
* <p>
* 手指按下 文字變色
* 重寫onTouchEvent方法 返回true
* 在Down和move中 計算
* int touchIndex = Y/itemHeight 強制繪製
* <p>
* 2.在onDraw方法對於該下包畫筆變色
* <p>
* 3。在Up的時候 touchIndex = -1;
* 強制繪製
*/
public class IndexView extends View {
/**
* 每條的寬和高
*/
private int itemWidth;
private int itemHeight;
Paint paint;
private String[] words = {"A", "B", "C", "D", "E", "F", "G", "H", "I",
"J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V",
"W", "X", "Y", "Z"};
/**
* 字母的下標位置
* */
private int touchIndex = -1;
public IndexView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
initView(context);
}
private void initView(Context context) {
paint = new Paint();
paint.setColor(Color.BLACK);//設置顏色
paint.setAntiAlias(true);
paint.setTypeface(Typeface.DEFAULT_BOLD);
}
/**
* 測量方法
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
itemWidth = getMeasuredWidth();
itemHeight = getMeasuredHeight() / words.length;
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
for (int i = 0; i < words.length; i++) {
if(touchIndex == i){
//設置灰色
paint.setColor(Color.GRAY);
}else{
//設置白色
paint.setColor(Color.BLACK);
}
String word = words[i];
Rect rect = new Rect();
//0,1 取一個字母
paint.getTextBounds(word, 0, 1, rect);
int wordWidth = rect.width();
int wordHeight = rect.height();
//計算每個字母在視圖上的座標
float wordX = itemWidth / 2 - wordWidth / 2;
float wordY = itemHeight / 2 + wordHeight / 2 + i * itemHeight;
canvas.drawText(word, wordX, wordY, paint);
}
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
break;
case MotionEvent.ACTION_MOVE:
float Y= event.getY();
int index = (int) (Y/itemHeight);//字母的索引
if(index != touchIndex){
touchIndex = index;//強制繪製 onDraw();
invalidate();
if(onIndexChangeListener != null && touchIndex<words.length){
onIndexChangeListener.OnIndexChange(words[touchIndex]);
}
}
break;
case MotionEvent.ACTION_UP :
touchIndex = -1;
invalidate();
break;
}
return true;
}
/**
* 字母下標索引變化監聽器
* */
public interface OnIndexChangeListener{
/**
* 擋字幕下標位置發生變化時候回調
* */
void OnIndexChange(String word);
}
private OnIndexChangeListener onIndexChangeListener;
public void setOnIndexChangeListener(OnIndexChangeListener onIndexChangeListener) {
this.onIndexChangeListener = onIndexChangeListener;
}
}
5)通過activity實現該功能(MainActivity)
該類需要關注的點:
Toast顯示的時間
public class MainActivity extends AppCompatActivity {
private ListView lvMain;
private TextView tvWord;
private IndexView ivWords;
private Handler handler = new Handler();
/**
* 聯繫人的集合
*/
private ArrayList<Person> persons;
private IndexAdapter adapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();
}
private void initView() {
lvMain = findViewById(R.id.lv_main);
tvWord = findViewById(R.id.tv_word);
ivWords = findViewById(R.id.iv_words);
//設置監聽字母下標索引變化
ivWords.setOnIndexChangeListener(new IndexView.OnIndexChangeListener() {
/**
* 回傳是字母
* */
@Override
public void OnIndexChange(String word) {
updateWord(word);
updateListView(word);//A~Z
}
});
initData();
adapter = new IndexAdapter();
lvMain.setAdapter(adapter);
}
private void updateListView(String word) {
for(int i=0;i<persons.size();i++){
String listWord = persons.get(i).getPinyin().substring(0,1);//YANGGUANGFU-->Y
if (word.equals(listWord)) {
//i是listView中的位置
lvMain.setSelection(i);//定位到ListVeiw中的某個位置
return;
}
}
}
private void updateWord(String word){
//顯示
tvWord.setVisibility(View.VISIBLE);
tvWord.setText(word);
handler.removeCallbacksAndMessages(null);
handler.postDelayed(new Runnable() {
@Override
public void run() {
//也是運行在主線程中
tvWord.setVisibility(View.GONE);
}
},500);
}
/**
* 初始化數據
*/
private void initData() {
persons = new ArrayList<>();
persons.add(new Person("張曉飛"));
persons.add(new Person("楊光福"));
persons.add(new Person("胡繼羣"));
persons.add(new Person("劉暢"));
persons.add(new Person("鍾澤興"));
persons.add(new Person("尹革新"));
persons.add(new Person("安傳鑫"));
persons.add(new Person("張騫壬"));
persons.add(new Person("溫松"));
persons.add(new Person("李鳳秋"));
persons.add(new Person("劉甫"));
persons.add(new Person("婁全超"));
persons.add(new Person("張猛"));
persons.add(new Person("王英傑"));
persons.add(new Person("李振南"));
persons.add(new Person("孫仁政"));
persons.add(new Person("唐春雷"));
persons.add(new Person("牛鵬偉"));
persons.add(new Person("姜宇航"));
persons.add(new Person("劉挺"));
persons.add(new Person("張洪瑞"));
persons.add(new Person("張建忠"));
persons.add(new Person("侯亞帥"));
persons.add(new Person("劉帥"));
persons.add(new Person("喬競飛"));
persons.add(new Person("徐雨健"));
persons.add(new Person("吳亮"));
persons.add(new Person("王兆霖"));
persons.add(new Person("阿三"));
persons.add(new Person("李博俊"));
//排序
Collections.sort(persons, new Comparator<Person>() {
@Override
public int compare(Person o1, Person o2) {
return o1.getPinyin().compareTo(o2.getPinyin());
}
});
}
class IndexAdapter extends BaseAdapter{
@Override
public int getCount() {
return persons.size();
}
@Override
public Object getItem(int position) {
return null;
}
@Override
public long getItemId(int position) {
return 0;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder viewHolder;
if(convertView == null){
convertView = View.inflate(MainActivity.this,R.layout.item_main,null);
viewHolder = new ViewHolder();
viewHolder.tv_word = convertView.findViewById(R.id.tv_word);
viewHolder.tv_name = convertView.findViewById(R.id.tv_name);
convertView.setTag(viewHolder);
}else{
viewHolder = (ViewHolder) convertView.getTag();
}
String name = persons.get(position).getName();//阿福
String word = persons.get(position).getPinyin().substring(0,1);//AFU->A
viewHolder.tv_word.setText(word);
viewHolder.tv_name.setText(name);
if(position ==0){
viewHolder.tv_word.setVisibility(View.VISIBLE);
}else{
//得到前一個位置對應的字母,如果當前的字母和上一個相同,隱藏;否則就顯示
String preWord = persons.get(position-1).getPinyin().substring(0,1);//A~Z
if(word.equals(preWord)){
viewHolder.tv_word.setVisibility(View.GONE);
}else{
viewHolder.tv_word.setVisibility(View.VISIBLE);
}
}
return convertView;
}
}
static class ViewHolder{
TextView tv_word;
TextView tv_name;
}
}
到這裏功能可以說已經介紹完了。把剩下的xml文件贈送給大家
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.zzsy.quickindex.MainActivity">
<ListView
android:id="@+id/lv_main"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
<TextView
android:id="@+id/tv_word"
android:visibility="gone"
android:layout_centerInParent="true"
android:layout_width="80dp"
android:layout_height="80dp"
android:gravity="center"
android:background="#44000000"
android:textColor="#000000"
android:text="A"
android:textSize="30sp"
android:textStyle="bold"
/>
<com.zzsy.quickindex.IndexView
android:id="@+id/iv_words"
android:layout_width="35dp"
android:layout_height="match_parent"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:layout_alignParentTop="true"
android:background="#10000000" />
</RelativeLayout>
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:id="@+id/tv_word"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#22000000"
android:text="A"
android:paddingLeft="5dp"
android:textColor="#000000"
android:textSize="25sp" />
<TextView
android:id="@+id/tv_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="阿福"
android:padding="5dp"
android:textColor="#dd000000"
android:textSize="22sp" />
</LinearLayout>
上面的xml文件分別是main,item_main
五。jar包地址: