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

這是比較常見(jiàn)的效果了吧
列表根據(jù)首字符的拼音字母來(lái)排序,且可以通過(guò)側(cè)邊欄的字母索引來(lái)進(jìn)行定位。
實(shí)現(xiàn)這樣一個(gè)效果并不難,只要自定義一個(gè)索引View,然后引入一個(gè)可以對(duì)漢字進(jìn)行拼音解析的jar包——pinyin4j-2.5.0即可
首先,先來(lái)定義側(cè)邊欄控件View,只要直接畫出來(lái)即可。
字母選中項(xiàng)會(huì)變?yōu)榧t色,且滑動(dòng)時(shí)背景會(huì)變色,此時(shí)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();
//平均每個(gè)字母占的高度
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è)邊欄索引條而已,不包含居中的提示文本,這個(gè)在另一個(gè)布局添加即可
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通過(guò)回調(diào)接口來(lái)更新居中TextView的文本內(nèi)容和可見(jiàn)性
使用到的布局
<?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>
此時(shí)就完成了索引View的繪制,不過(guò)定位功能還需要再通過(guò)回調(diào)接口來(lái)完成。
引入jar包后,先來(lái)設(shè)定一個(gè)工具類,包含一個(gè)可以解析字符串的方法,返回值為首字符對(duì)應(yīng)的拼音首字母或者為包含一個(gè)空格的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);
}
// 漢語(yǔ)拼音格式輸出類
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 ' ';
}
}
然后再定義一個(gè)實(shí)體類,包含用戶名,電話,用戶名首字符的拼音首字母等三個(gè)屬性
需要實(shí)現(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,還需要定義一個(gè)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)系人列表中第一個(gè)首字符為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();
}
這樣,整個(gè)效果就都完成了。
這里提供代碼下載:Android 仿微信通訊錄列表側(cè)邊欄
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
基于Android studio3.6的JNI教程之ncnn人臉檢測(cè)mtcnn功能
這篇文章主要介紹了基于Android studio3.6的JNI教程之ncnn之人臉檢測(cè)mtcnn功能,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-03-03
Android開發(fā)人臉識(shí)別統(tǒng)計(jì)人臉數(shù)
這篇文章主要介紹了Android開發(fā)人臉識(shí)別統(tǒng)計(jì)人臉數(shù),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-10-10
Android實(shí)現(xiàn)生成二維碼并保存到相冊(cè)
這篇文章主要介紹了如何利用Android實(shí)現(xiàn)二維碼的生成,并且保存到本地相冊(cè)。文中的示例代碼講解詳細(xì),感興趣的小伙伴快跟隨小編學(xué)習(xí)一下2022-04-04
Android如何實(shí)現(xiàn)設(shè)備的異顯功能詳解
這篇文章主要給大家介紹了關(guān)于Android如何實(shí)現(xiàn)設(shè)備的異顯功能的相關(guān)資料,這篇文章通過(guò)示例代碼介紹的非常詳細(xì),對(duì)各位Android開發(fā)者們具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2022-02-02
android內(nèi)存優(yōu)化之圖片優(yōu)化
對(duì)圖片本身進(jìn)行操作。盡量不要使用setImageBitmap、setImageResource、BitmapFactory.decodeResource來(lái)設(shè)置一張大圖,因?yàn)檫@些方法在完成decode后,最終都是通過(guò)java層的createBitmap來(lái)完成的,需要消耗更多內(nèi)存2012-12-12
Rxjava2_Flowable_Sqlite_Android數(shù)據(jù)庫(kù)訪問(wèn)實(shí)例
下面小編就為大家分享一篇Rxjava2_Flowable_Sqlite_Android數(shù)據(jù)庫(kù)訪問(wèn)實(shí)例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2018-02-02

