Android仿微信通訊錄列表側(cè)邊欄效果
先看Android仿微信通訊錄列表側(cè)邊欄效果圖

這是比較常見的效果了吧
列表根據(jù)首字符的拼音字母來排序,且可以通過側(cè)邊欄的字母索引來進(jìn)行定位。
實現(xiàn)這樣一個效果并不難,只要自定義一個索引View,然后引入一個可以對漢字進(jìn)行拼音解析的jar包——pinyin4j-2.5.0即可
首先,先來定義側(cè)邊欄控件View,只要直接畫出來即可。
字母選中項會變?yōu)榧t色,且滑動時背景會變色,此時SideBar并不包含居中的提示文本
public class SideBar extends View {
private Paint paint = new Paint();
private int choose = -1;
private boolean showBackground;
public static String[] letters = {"#", "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 OnChooseLetterChangedListener onChooseLetterChangedListener;
public SideBar(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public SideBar(Context context, AttributeSet attrs) {
super(context, attrs);
}
public SideBar(Context context) {
super(context);
}
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (showBackground) {
canvas.drawColor(Color.parseColor("#D9D9D9"));
}
int height = getHeight();
int width = getWidth();
//平均每個字母占的高度
int singleHeight = height / letters.length;
for (int i = 0; i < letters.length; i++) {
paint.setColor(Color.BLACK);
paint.setAntiAlias(true);
paint.setTextSize(25);
if (i == choose) {
paint.setColor(Color.parseColor("#FF2828"));
paint.setFakeBoldText(true);
}
float x = width / 2 - paint.measureText(letters[i]) / 2;
float y = singleHeight * i + singleHeight;
canvas.drawText(letters[i], x, y, paint);
paint.reset();
}
}
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
int action = event.getAction();
float y = event.getY();
int oldChoose = choose;
int c = (int) (y / getHeight() * letters.length);
switch (action) {
case MotionEvent.ACTION_DOWN:
showBackground = true;
if (oldChoose != c && onChooseLetterChangedListener != null) {
if (c > -1 && c < letters.length) {
onChooseLetterChangedListener.onChooseLetter(letters[c]);
choose = c;
invalidate();
}
}
break;
case MotionEvent.ACTION_MOVE:
if (oldChoose != c && onChooseLetterChangedListener != null) {
if (c > -1 && c < letters.length) {
onChooseLetterChangedListener.onChooseLetter(letters[c]);
choose = c;
invalidate();
}
}
break;
case MotionEvent.ACTION_UP:
showBackground = false;
choose = -1;
if (onChooseLetterChangedListener != null) {
onChooseLetterChangedListener.onNoChooseLetter();
}
invalidate();
break;
}
return true;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
return super.onTouchEvent(event);
}
public void setOnTouchingLetterChangedListener(OnChooseLetterChangedListener onChooseLetterChangedListener) {
this.onChooseLetterChangedListener = onChooseLetterChangedListener;
}
public interface OnChooseLetterChangedListener {
void onChooseLetter(String s);
void onNoChooseLetter();
}
}
SideBar只是畫出了側(cè)邊欄索引條而已,不包含居中的提示文本,這個在另一個布局添加即可
public class HintSideBar extends RelativeLayout implements SideBar.OnChooseLetterChangedListener {
private TextView tv_hint;
private SideBar.OnChooseLetterChangedListener onChooseLetterChangedListener;
public HintSideBar(Context context, AttributeSet attrs) {
super(context, attrs);
LayoutInflater.from(context).inflate(R.layout.view_hint_side_bar, this);
initView();
}
private void initView() {
SideBar sideBar = (SideBar) findViewById(R.id.sideBar);
tv_hint = (TextView) findViewById(R.id.tv_hint);
sideBar.setOnTouchingLetterChangedListener(this);
}
@Override
public void onChooseLetter(String s) {
tv_hint.setText(s);
tv_hint.setVisibility(VISIBLE);
if (onChooseLetterChangedListener != null) {
onChooseLetterChangedListener.onChooseLetter(s);
}
}
@Override
public void onNoChooseLetter() {
tv_hint.setVisibility(INVISIBLE);
if (onChooseLetterChangedListener != null) {
onChooseLetterChangedListener.onNoChooseLetter();
}
}
public void setOnChooseLetterChangedListener(SideBar.OnChooseLetterChangedListener onChooseLetterChangedListener) {
this.onChooseLetterChangedListener = onChooseLetterChangedListener;
}
}
HintSideBar通過回調(diào)接口來更新居中TextView的文本內(nèi)容和可見性
使用到的布局
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.czy.demo.SideBar
android:id="@+id/sideBar"
android:layout_width="30dp"
android:layout_height="match_parent"
android:layout_alignParentRight="true" />
<TextView
android:id="@+id/tv_hint"
android:layout_width="70dp"
android:layout_height="70dp"
android:layout_centerInParent="true"
android:background="#4b0e0e0e"
android:gravity="center"
android:textColor="#ffffff"
android:textSize="30sp"
android:visibility="invisible" />
</RelativeLayout>
此時就完成了索引View的繪制,不過定位功能還需要再通過回調(diào)接口來完成。
引入jar包后,先來設(shè)定一個工具類,包含一個可以解析字符串的方法,返回值為首字符對應(yīng)的拼音首字母或者為包含一個空格的char類型數(shù)據(jù)。
public class Utils {
/**
* 如果字符串的首字符為漢字,則返回該漢字的拼音大寫首字母
* 如果字符串的首字符為字母,也轉(zhuǎn)化為大寫字母返回
* 其他情況均返回' '
*
* @param str 字符串
* @return 首字母
*/
public static char getHeadChar(String str) {
if (str != null && str.trim().length() != 0) {
char[] strChar = str.toCharArray();
char headChar = strChar[0];
//如果是大寫字母則直接返回
if (Character.isUpperCase(headChar)) {
return headChar;
} else if (Character.isLowerCase(headChar)) {
return Character.toUpperCase(headChar);
}
// 漢語拼音格式輸出類
HanyuPinyinOutputFormat hanYuPinOutputFormat = new HanyuPinyinOutputFormat();
hanYuPinOutputFormat.setCaseType(UPPERCASE);
hanYuPinOutputFormat.setToneType(WITHOUT_TONE);
if (String.valueOf(headChar).matches("[\\u4E00-\\u9FA5]+")) {
try {
String[] stringArray = PinyinHelper.toHanyuPinyinStringArray(headChar, hanYuPinOutputFormat);
if (stringArray != null && stringArray[0] != null) {
return stringArray[0].charAt(0);
}
} catch (BadHanyuPinyinOutputFormatCombination e) {
return ' ';
}
}
}
return ' ';
}
}
然后再定義一個實體類,包含用戶名,電話,用戶名首字符的拼音首字母等三個屬性
需要實現(xiàn)Comparable 接口,用于排序
public class User implements Comparable {
private String userName;
private String phone;
private char headLetter;
public User(String userName, String phone) {
this.userName = userName;
this.phone = phone;
headLetter = Utils.getHeadChar(userName);
}
public String getUserName() {
return userName;
}
public String getPhone() {
return phone;
}
public char getHeadLetter() {
return headLetter;
}
@Override
public boolean equals(Object object) {
if (this == object) {
return true;
}
if (object == null || getClass() != object.getClass()) {
return false;
}
User that = (User) object;
return getUserName().equals(that.getUserName()) && getPhone().equals(that.getPhone());
}
@Override
public int compareTo(Object object) {
if (object instanceof User) {
User that = (User) object;
if (getHeadLetter() == ' ') {
if (that.getHeadLetter() == ' ') {
return 0;
}
return -1;
}
if (that.getHeadLetter() == ' ') {
return 1;
} else if (that.getHeadLetter() > getHeadLetter()) {
return -1;
} else if (that.getHeadLetter() == getHeadLetter()) {
return 0;
}
return 1;
} else {
throw new ClassCastException();
}
}
}
主布局文件如下
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v7.widget.RecyclerView
android:id="@+id/rv_userList"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<com.czy.demo.HintSideBar
android:id="@+id/hintSideBar"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="right" />
</FrameLayout>
聯(lián)系人列表使用的是RecyclerView,還需要定義一個Adapter
public class UserAdapter extends RecyclerView.Adapter<UserAdapter.UserHolder> {
private List<User> userList;
private LayoutInflater inflater;
public UserAdapter(Context context) {
inflater = LayoutInflater.from(context);
userList = new ArrayList<>();
}
@Override
public UserHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = inflater.inflate(R.layout.item_user, parent, false);
return new UserHolder(view);
}
@Override
public void onBindViewHolder(UserHolder holder, int position) {
holder.tv_userName.setText(userList.get(position).getUserName());
holder.tv_phone.setText(userList.get(position).getPhone());
}
public void setData(List<User> userList) {
this.userList.clear();
this.userList = userList;
}
public int getFirstPositionByChar(char sign) {
if (sign == '#') {
return 0;
}
for (int i = 0; i < userList.size(); i++) {
if (userList.get(i).getHeadLetter() == sign) {
return i;
}
}
return -1;
}
@Override
public int getItemCount() {
return userList.size();
}
class UserHolder extends RecyclerView.ViewHolder {
public TextView tv_userName;
public TextView tv_phone;
public UserHolder(View itemView) {
super(itemView);
tv_userName = (TextView) itemView.findViewById(R.id.tv_userName);
tv_phone = (TextView) itemView.findViewById(R.id.tv_phone);
}
}
}
以下方法用于獲取聯(lián)系人列表中第一個首字符為sign的item的位置
public int getFirstPositionByChar(char sign)
主Activity代碼如下
public class MainActivity extends AppCompatActivity implements SideBar.OnChooseLetterChangedListener {
private List<User> userList;
private UserAdapter adapter;
private RecyclerView rv_userList;
private LinearLayoutManager manager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
supportRequestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.activity_main);
HintSideBar hintSideBar = (HintSideBar) findViewById(R.id.hintSideBar);
rv_userList = (RecyclerView) findViewById(R.id.rv_userList);
hintSideBar.setOnChooseLetterChangedListener(this);
manager = new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false);
rv_userList.setLayoutManager(manager);
userList = new ArrayList<>();
adapter = new UserAdapter(this);
initData();
adapter.setData(userList);
rv_userList.setAdapter(adapter);
}
@Override
public void onChooseLetter(String s) {
int i = adapter.getFirstPositionByChar(s.charAt(0));
if (i == -1) {
return;
}
manager.scrollToPositionWithOffset(i, 0);
}
@Override
public void onNoChooseLetter() {
}
}
initData()用于向Adapter填充數(shù)據(jù)
public void initData() {
User user1 = new User("陳", "12345678");
User user2 = new User("趙", "12345678");
...
userList.add(user1);
userList.add(user2);
...
Collections.sort(userList);
adapter.notifyDataSetChanged();
}
這樣,整個效果就都完成了。
這里提供代碼下載:Android 仿微信通訊錄列表側(cè)邊欄
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
Android Studio之Debug運行期代碼植入的方法
這篇文章主要介紹了Android Studio之Debug運行期代碼植入的方法,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-07-07
Android ScrollView滑動實現(xiàn)仿QQ空間標(biāo)題欄漸變
這篇文章主要為大家詳細(xì)介紹了Android ScrollView滑動實現(xiàn)仿QQ空間標(biāo)題欄漸變,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2016-08-08
Android WorkManager實現(xiàn)后臺定時任務(wù)流程詳解
WorkManager是Android Jetpack的一個強大的組件,用于處理后臺耗時任務(wù)。后臺任務(wù)可以是一次性的,也可以是重復(fù)的,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)吧2023-01-01
Android中實現(xiàn)為TextView添加多個可點擊的文本
這篇文章主要介紹了Android中實現(xiàn)為TextView添加多個可點擊的文本,可實現(xiàn)類似Android社交軟件顯示點贊用戶并通過用戶名稱進(jìn)入該用戶主頁的功能,是非常實用的技巧,需要的朋友可以參考下2014-09-09
android中ListView多次刷新重復(fù)執(zhí)行g(shù)etView的解決方法
以前倒是沒有注意listview的getView會重復(fù)執(zhí)行多次,在測試的時候去斷點跟蹤,發(fā)現(xiàn)同一條數(shù)據(jù)不斷的重復(fù)執(zhí)行,下面與大家分享下正確的解決方法,希望對你有所幫助2013-06-06
android基礎(chǔ)總結(jié)篇之九:Intent應(yīng)用詳解
這篇文章主要介紹了android基礎(chǔ)總結(jié)篇之九:Intent應(yīng)用詳解,有需要的可以了解一下。2016-11-11
Android使用google breakpad捕獲分析native cash
這篇文章主要介紹了Android使用google breakpad捕獲分析native cash 的相關(guān)知識,非常不錯,具有一定的參考借鑒價值,需要的朋友可以參考下2019-04-04

