安卓模仿微信通訊錄--RecyclerView+SideBar

 

需要一個依賴,導入把中文轉爲拼音的功能

implementation 'com.belerweb:pinyin4j:2.5.1'

在values創建一個attrs

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="SideBar">
        <attr name="scaleSize" format="integer"/>
        <attr name="scaleItemCount" format="integer"/>
        <attr name="scaleWidth" format="dimension"/>
    </declare-styleable>
</resources>

兩個工具類,直接複製粘貼就行



import net.sourceforge.pinyin4j.PinyinHelper;
import net.sourceforge.pinyin4j.format.HanyuPinyinCaseType;
import net.sourceforge.pinyin4j.format.HanyuPinyinOutputFormat;
import net.sourceforge.pinyin4j.format.HanyuPinyinToneType;

/**
 * 漢字轉換位漢語拼音,英文字符不變
 */
public class Cn2Spell {

    public static StringBuffer sb = new StringBuffer();

    /**
     * 獲取漢字字符串的首字母,英文字符不變
     * 例如:阿飛→af
     */
    public static String getPinYinHeadChar(String chines) {
        sb.setLength(0);
        char[] chars = chines.toCharArray();
        HanyuPinyinOutputFormat defaultFormat = new HanyuPinyinOutputFormat();
        defaultFormat.setCaseType(HanyuPinyinCaseType.LOWERCASE);
        defaultFormat.setToneType(HanyuPinyinToneType.WITHOUT_TONE);
        for (int i = 0; i < chars.length; i++) {
            if (chars[i] > 128) {
                try {
                    sb.append(PinyinHelper.toHanyuPinyinStringArray(chars[i], defaultFormat)[0].charAt(0));
                } catch (Exception e) {
                    e.printStackTrace();
                }
            } else {
                sb.append(chars[i]);
            }
        }
        return sb.toString();
    }

    /**
     * 獲取漢字字符串的第一個字母
     */
    public static String getPinYinFirstLetter(String str) {
        sb.setLength(0);
        char c = str.charAt(0);
        String[] pinyinArray = PinyinHelper.toHanyuPinyinStringArray(c);
        if (pinyinArray != null) {
            sb.append(pinyinArray[0].charAt(0));
        } else {
            sb.append(c);
        }
        return sb.toString();
    }

    /**
     * 獲取漢字字符串的漢語拼音,英文字符不變
     */
    public static String getPinYin(String chines) {
        sb.setLength(0);
        char[] nameChar = chines.toCharArray();
        HanyuPinyinOutputFormat defaultFormat = new HanyuPinyinOutputFormat();
        defaultFormat.setCaseType(HanyuPinyinCaseType.LOWERCASE);
        defaultFormat.setToneType(HanyuPinyinToneType.WITHOUT_TONE);
        for (int i = 0; i < nameChar.length; i++) {
            if (nameChar[i] > 128) {
                try {
                    sb.append(PinyinHelper.toHanyuPinyinStringArray(nameChar[i], defaultFormat)[0]);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            } else {
                sb.append(nameChar[i]);
            }
        }
        return sb.toString();
    }

}


import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.widget.TextView;

import com.example.wechat.R;

@SuppressLint("AppCompatCustomView")
public class SideBar extends TextView {
    private String[] letters = new String[]{"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 Paint textPaint;
    private Paint bigTextPaint;
    private Paint scaleTextPaint;

    private Canvas canvas;
    private int itemH;
    private int w;
    private int h;
    /**
     * 普通情況下字體大小
     */
    float singleTextH;
    /**
     * 縮放離原始的寬度
     */
    private float scaleWidth;
    /**
     * 滑動的Y
     */
    private float eventY = 0;
    /**
     * 縮放的倍數
     */
    private int scaleSize = 1;
    /**
     * 縮放個數item,即開口大小
     */
    private int scaleItemCount = 6;
    private ISideBarSelectCallBack callBack;

    public SideBar(Context context) {
        this(context, null);
    }

    public SideBar(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public SideBar(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(attrs);
    }

    private void init(AttributeSet attrs) {
        if (attrs != null) {
            TypedArray ta = getContext().obtainStyledAttributes(attrs, R.styleable.SideBar);
            scaleSize = ta.getInteger(R.styleable.SideBar_scaleSize, 1);
            scaleItemCount = ta.getInteger(R.styleable.SideBar_scaleItemCount, 6);
            scaleWidth = ta.getDimensionPixelSize(R.styleable.SideBar_scaleWidth, dp(100));
            ta.recycle();
        }
        textPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        textPaint.setColor(getCurrentTextColor());
        textPaint.setTextSize(getTextSize());
        textPaint.setTextAlign(Paint.Align.CENTER);
        bigTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        bigTextPaint.setColor(getCurrentTextColor());
        bigTextPaint.setTextSize(getTextSize() * (scaleSize + 3));
        bigTextPaint.setTextAlign(Paint.Align.CENTER);
        scaleTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        scaleTextPaint.setColor(getCurrentTextColor());
        scaleTextPaint.setTextSize(getTextSize() * (scaleSize + 1));
        scaleTextPaint.setTextAlign(Paint.Align.CENTER);
    }

    public void setDataResource(String[] data) {
        letters = data;
        invalidate();
    }

    public void setOnStrSelectCallBack(ISideBarSelectCallBack callBack) {
        this.callBack = callBack;
    }

    /**
     * 設置字體縮放比例
     *
     * @param scale
     */
    public void setScaleSize(int scale) {
        scaleSize = scale;
        invalidate();
    }

    /**
     * 設置縮放字體的個數,即開口大小
     *
     * @param scaleItemCount
     */
    public void setScaleItemCount(int scaleItemCount) {
        this.scaleItemCount = scaleItemCount;
        invalidate();
    }

    private int dp(int px) {
        final float scale = getContext().getResources().getDisplayMetrics().density;
        return (int) (px * scale + 0.5f);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
            case MotionEvent.ACTION_MOVE:
                if (event.getX() > (w - getPaddingRight() - singleTextH - 10)) {
                    eventY = event.getY();
                    invalidate();
                    return true;
                } else {
                    eventY = 0;
                    invalidate();
                    break;
                }
            case MotionEvent.ACTION_CANCEL:
                eventY = 0;
                invalidate();
                return true;
            case MotionEvent.ACTION_UP:
                if (event.getX() > (w - getPaddingRight() - singleTextH - 10)) {
                    eventY = 0;
                    invalidate();
                    return true;
                } else
                    break;
        }
        return super.onTouchEvent(event);
    }


    @Override
    protected void onDraw(Canvas canvas) {
        this.canvas = canvas;
        DrawView(eventY);
    }

    private void DrawView(float y) {
        int currentSelectIndex = -1;
        if (y != 0) {
            for (int i = 0; i < letters.length; i++) {
                float currentItemY = itemH * i;
                float nextItemY = itemH * (i + 1);
                if (y >= currentItemY && y < nextItemY) {
                    currentSelectIndex = i;
                    if (callBack != null) {
                        callBack.onSelectStr(currentSelectIndex, letters[i]);
                    }
                    //畫大的字母
                    Paint.FontMetrics fontMetrics = bigTextPaint.getFontMetrics();
                    float bigTextSize = fontMetrics.descent - fontMetrics.ascent;
                    canvas.drawText(letters[i], w - getPaddingRight() - scaleWidth - bigTextSize, singleTextH + itemH * i, bigTextPaint);
                }
            }
        }
        drawLetters(y, currentSelectIndex);
    }

    private void drawLetters(float y, int index) {
        //第一次進來沒有縮放情況,默認畫原圖
        if (index == -1) {
            w = getMeasuredWidth();
            h = getMeasuredHeight();
            itemH = h / letters.length;
            Paint.FontMetrics fontMetrics = textPaint.getFontMetrics();
            singleTextH = fontMetrics.descent - fontMetrics.ascent;
            for (int i = 0; i < letters.length; i++) {
                canvas.drawText(letters[i], w - getPaddingRight(), singleTextH + itemH * i, textPaint);
            }
            //觸摸的時候畫縮放圖
        } else {
            //遍歷所有字母
            for (int i = 0; i < letters.length; i++) {
                //要畫的字母的起始Y座標
                float currentItemToDrawY = singleTextH + itemH * i;
                float centerItemToDrawY;
                if (index < i)
                    centerItemToDrawY = singleTextH + itemH * (index + scaleItemCount);
                else
                    centerItemToDrawY = singleTextH + itemH * (index - scaleItemCount);
                float delta = 1 - Math.abs((y - currentItemToDrawY) / (centerItemToDrawY - currentItemToDrawY));
                float maxRightX = w - getPaddingRight();
                //如果大於0,表明在y座標上方
                scaleTextPaint.setTextSize(getTextSize() + getTextSize() * delta);
                float drawX = maxRightX - scaleWidth * delta;
                //超出邊界直接花在邊界上
                if (drawX > maxRightX)
                    canvas.drawText(letters[i], maxRightX, singleTextH + itemH * i, textPaint);
                else
                    canvas.drawText(letters[i], drawX, singleTextH + itemH * i, scaleTextPaint);
            }
        }
    }

    public interface ISideBarSelectCallBack {
        void onSelectStr(int index, String selectStr);
    }

}

 

前期工作做好了,然後界面

<?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">

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/userlist"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

    <com.example.wechat.util.SideBar
        android:id="@+id/sidebar"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_alignParentRight="true"
        android:paddingRight="10dp"
        android:textColor="@color/colorGreen"
        android:textSize="15sp" />


</RelativeLayout>

item,這個根佈局需要用相對佈局,線性佈局老是有bug,我也不知道原因,反正換成相對佈局就正常顯示了

<?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="wrap_content">

    <LinearLayout
        android:id="@+id/letterlayout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@color/colorLightGray">

        <TextView
            android:id="@+id/letter"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:padding="6dp"
            android:text="A"
            android:textColor="#646262"
            android:textSize="20sp" />
    </LinearLayout>


    <LinearLayout
        android:id="@+id/layout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_below="@id/letterlayout"
        android:orientation="vertical">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="horizontal">

            <com.example.wechat.util.RoundImageView
                android:id="@+id/header"
                android:layout_width="55dp"
                android:layout_height="55dp"
                android:layout_margin="5dp"
                android:src="@drawable/head_test" />

            <TextView
                android:id="@+id/name"
                android:layout_width="wrap_content"
                android:layout_height="match_parent"
                android:gravity="center_vertical"
                android:padding="10dp"
                android:text="@string/app_name"
                android:textColor="@color/colorBlack"
                android:textSize="20sp" />

        </LinearLayout>

        <View
            android:id="@+id/underline"
            android:layout_width="match_parent"
            android:layout_height="0.5dp"
            android:layout_below="@id/name"
            android:layout_marginLeft="60dp"
            android:background="@color/colorLightGray"/>

    </LinearLayout>


</RelativeLayout>

User在這個功能裏用到的屬性只有name,headerid,pinyin和start,兩個接口一個是用來傳遞消息,一個用來排序

package com.example.wechat.bean;

import com.example.wechat.util.Cn2Spell;

import java.io.Serializable;
import java.util.Date;

public class User implements Serializable, Comparable<User> {
    String name;    //名字
    String content; //內容
    String time; //日期
    int headerid; //頭像
    String pinyin;  //中文轉換爲拼音
    String start; //首字母

    public User(String name, String content, String time, int headerid) {
        this.name = name;
        this.content = content;
        this.time = time;
        this.headerid = headerid;
    }

    public User(String name,int headerid) {
        this.headerid = headerid;
        this.name = name;
        pinyin = Cn2Spell.getPinYin(name);
        start = pinyin.substring(0, 1).toUpperCase();
        if (!start.matches("[A-Z]")) {
            start = "#";
        }
    }


    public void setPinyin(String pinyin) {
        this.pinyin = pinyin;
    }

    public void setStart(String start) {
        this.start = start;
    }

    public String getPinyin() {
        return pinyin;
    }

    public String getStart() {
        return start;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }

    public String getTime() {
        return time;
    }

    public void setTime(String time) {
        this.time = time;
    }

    public int getHeaderid() {
        return headerid;
    }

    public void setHeaderid(int headerid) {
        this.headerid = headerid;
    }

    @Override
    public int compareTo(User user) {
        if (start.equals("#") && !user.getStart().equals("#")) {
            return 1;
        } else if (!start.equals("#") && user.getStart().equals("#")) {
            return -1;
        } else {
            return pinyin.compareToIgnoreCase(user.getPinyin());
        }
    }
}

Adapter,裏面有一點邏輯判斷什麼時候顯示字母的View,什麼時候顯示下劃線,很簡單的邏輯一看就懂

package com.example.wechat.adapter;

import android.content.Context;
import android.content.Intent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;

import com.example.wechat.R;
import com.example.wechat.activity.ItemDetail;
import com.example.wechat.bean.User;
import com.example.wechat.util.RoundImageView;

import java.util.ArrayList;
import java.util.List;

import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;

public class FriendAdapter extends RecyclerView.Adapter<FriendAdapter.ViewHolder> {
    List<User> users = new ArrayList<>();
    Context context;
    LayoutInflater inflater;

    public FriendAdapter(List<User> users, Context context) {
        this.users = users;
        this.context = context;
        inflater = LayoutInflater.from(context);
    }

    @NonNull
    @Override
    public FriendAdapter.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        View view = inflater.inflate(R.layout.friend_item, null);
        ViewHolder viewHolder = new ViewHolder(view);
        return viewHolder;
    }

    @Override
    public void onBindViewHolder(@NonNull FriendAdapter.ViewHolder holder, final int position) {
        final User user = users.get(position);
        holder.name.setText(user.getName());
        holder.header.setImageResource(user.getHeaderid());
        holder.view.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(context, ItemDetail.class);
                intent.putExtra("user_data",user);
                context.startActivity(intent);
            }
        });

        String mark = user.getStart();
        if (position == getPosition(mark)){
            holder.letterlayout.setVisibility(View.VISIBLE);
            holder.letter.setText(user.getStart());
        }else {
            holder.letterlayout.setVisibility(View.GONE);
        }

        if (position!=getItemCount()-1&&user.getStart().equalsIgnoreCase(users.get(position+1).getStart())){
            holder.underline.setVisibility(View.VISIBLE);
        }else {
            holder.underline.setVisibility(View.GONE);
        }


    }

    private int getPosition(String mark) {
        for (int i=0;i<getItemCount();i++){
            if (users.get(i).getStart().equalsIgnoreCase(mark)){
                return i;
            }
        }
        return -1;
    }

    @Override
    public int getItemCount() {
        return users.size();
    }

    public class ViewHolder extends RecyclerView.ViewHolder {
        LinearLayout letterlayout;
        TextView letter;
        TextView name;
        RoundImageView header;
        View view;
        View underline;
        public ViewHolder(@NonNull View itemView) {
            super(itemView);
            view = itemView;
            underline = itemView.findViewById(R.id.underline);
            letterlayout = itemView.findViewById(R.id.letterlayout);
            letter = itemView.findViewById(R.id.letter);
            name = itemView.findViewById(R.id.name);
            header = itemView.findViewById(R.id.header);
        }
    }
}

Fragment

package com.example.wechat.fragment;

import android.content.Context;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

import com.example.wechat.R;
import com.example.wechat.adapter.FriendAdapter;
import com.example.wechat.bean.User;
import com.example.wechat.util.SideBar;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;

public class FriendFragment extends Fragment {
    RecyclerView userlist;
    SideBar sideBar;
    FriendAdapter friendAdapter;
    List<User> users = new ArrayList<>();

    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.friendfragment, container, false);
        userlist = view.findViewById(R.id.userlist);
        sideBar = view.findViewById(R.id.sidebar);
        users.clear();
        initData();
        initView();
        return view;
    }

    private void initView() {
        LinearLayoutManager linearLayoutManager = new LinearLayoutManager(getContext());
        userlist.setLayoutManager(linearLayoutManager);
        friendAdapter = new FriendAdapter(users, getContext());
        userlist.setAdapter(friendAdapter);

        sideBar.setOnStrSelectCallBack(new SideBar.ISideBarSelectCallBack() {
            @Override
            public void onSelectStr(int index, String selectStr) {
                for (int i = 0; i < users.size(); i++) {
                    if (selectStr.equalsIgnoreCase(users.get(i).getStart())){
                    userlist.scrollToPosition(i);
                    return;
                    }
                }
            }
        });
    }

    private void initData() {
        users.add(new User("麗麗",R.drawable.head_test));
        users.add(new User("媽媽",R.drawable.mama));
        users.add(new User("爸爸",R.drawable.baba));
        users.add(new User("#xx",R.drawable.head_test));
        users.add(new User("虹貓",R.drawable.head_test));
        users.add(new User("藍兔",R.drawable.head_test));
        users.add(new User("阿牛",R.drawable.head_test));
        users.add(new User("CK",R.drawable.head_test));
        users.add(new User("燈虎",R.drawable.head_test));
        users.add(new User("爾康",R.drawable.head_test));
        users.add(new User("凡哥",R.drawable.head_test));
        users.add(new User("Gr",R.drawable.head_test));
        users.add(new User("123阿斯頓",R.drawable.head_test));
        Collections.sort(users);
    }
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章