一,介紹SectionIndexer
package android.widget;
/**
* 接口可以被adapter實現,使AbsListView的sections之間可以快速滑動
* 一個secton是一羣列表,有很多相似點,例如他們可能以相同的字母開頭,或者是來自同一個歌手的歌曲
* ExpandableListAdapters認爲groups和sections是同一個縮放組,應該返回一個合適的位置
* 可以看一下AbsListView中setFastScrollEnabled(boolean)方法
*/
public interface SectionIndexer {
//返回一個代表sections列表的非空的數組對象
//當滑動的時候,這個list view可以調用toString()來顯示預覽文本。
//例如,一個adapter可以返回代表字母表中字母的字符串數組,或者返回section titles的對象數組
Object[] getSections();
//提供section索引,通過section數組對象,返回adapter中section開始的位置
int getPositionForSection(int sectionIndex);
//提供adapter中的位置,在section數組對象中返回相應的section索引
//如果postion位置在索引開始位置之前,則返回0
int getSectionForPosition(int position);
}
這麼一說彷彿有點抽象,給個圖就知道了:
getSectionForPosition() 通過該項的位置,獲得所在分類組的索引號
getPositionForSection() 根據分類列的索引號獲得該序列的首個位置
二,具體實現
2.1 定義側面字母欄
public class SideBar extends AppCompatTextView {
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 interface ISideBarSelectCallBack {
void onSelectStr(int index, String selectStr);
}
public SideBar(Context context) {
super(context);
}
public SideBar(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
public SideBar(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(attrs);
}
/**
* 初始化函數
*
* @param attrs attr
*/
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;
}
/**
* 設置字體縮放比例
*/
public void setScaleSize(int scale) {
scaleSize = scale;
invalidate();
}
/**
* 設置縮放字體的個數,即開口大小
*/
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) {
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);
}
}
}
自定義view,主要作用就是,顯示側面的字母欄,後期點擊或者滑動時,能顯示出點擊的大字母。
2.2 xml佈局
聯繫人界面的xml佈局:
<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=".fragment.ChatFragment">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclercontactlistId"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
<com.Jason.materialdesign.widget.SideBar
android:id="@+id/sidebar"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_alignParentRight="true"
android:background="@android:color/transparent"
android:paddingRight="10dp"
android:textSize="15sp" />
</RelativeLayout>
2.3 實現 adapter 主要是實現SectionIndexer接口
這個adapter主要是聯繫人的item佈局,這個相對來說能理解,都直接原生庫原生接口,應該能明白。主要是就是實現sectionIndexer接口,這兒稍微注意點。
public class ContactsAdapter extends RecyclerView.Adapter<ContactsAdapter.ContactsListHolder> implements SectionIndexer {
/**
* 聯繫人分區
*/
private SparseIntArray mPositionOfSection;
/**
* 聯繫人分區
*/
private SparseIntArray mSectionOfPosition;
// 聯繫人列表
private List<ContactData> mContactDataList;
public ContactsAdapter(List<ContactData> contactDatas) {
this.mContactDataList = contactDatas;
}
@NonNull
@Override
public ContactsListHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.recycler_list_item_contact, parent,false);
ContactsListHolder holder = new ContactsListHolder(view);
return holder;
}
@Override
public void onBindViewHolder(@NonNull ContactsListHolder holder, int position) {
ContactData contactData = mContactDataList.get(position);
if (contactData != null){
holder.avaterView.setImageResource(R.mipmap.default_avatar);
holder.nameTxt.setText(contactData.getUserName());
}
}
@Override
public int getItemCount() {
return mContactDataList.size();
}
/**
*SectionIndexer分區方法
*/
@Override
public Object[] getSections() {
mPositionOfSection = new SparseIntArray();
mSectionOfPosition = new SparseIntArray();
// 傳入的聯繫人數量
int count = mContactDataList.size();
// list後期轉換爲array
List<String> list = new ArrayList<>();
list.add("");
mPositionOfSection.put(0, 0);
mSectionOfPosition.put(0, 0);
// 開始分區
for (int i = 1; i < count; i++)
{
String letter = mContactDataList.get(i).getFirstLetter();
int section = list.size() - 1;
if (list.get(section) != null && !list.get(section).equals(letter))
{
list.add(letter);
section++;
mPositionOfSection.put(section, i);
}
mSectionOfPosition.put(i, section);
}
return list.toArray(new String[list.size()]);
}
/**
*SectionIndexer分區方法
*/
@Override
public int getPositionForSection(int sectionIndex) {
return mPositionOfSection.get(sectionIndex);
}
/**
*SectionIndexer分區方法
*/
@Override
public int getSectionForPosition(int position) {
return mSectionOfPosition.get(position);
}
/**
* 普通聯繫人holder
*/
class ContactsListHolder extends RecyclerView.ViewHolder {
ImageView avaterView;
TextView nameTxt;
public ContactsListHolder(View view) {
super(view);
avaterView = (ImageView)view.findViewById(R.id.avatar);
nameTxt = (TextView)view.findViewById(R.id.name);
}
}
}
2.4 ContactFragment的代碼,包括一些初始化,比較排序等
public class ContactFragment extends Fragment {
private ContactsAdapter mContactsAdapter;
private RecyclerView mRecyclerView;
private SideBar mSideBar;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
View view = inflater.inflate(R.layout.fragment_contact, container, false);
initContacts(view);
return view;
}
/**
* 裝載數據
* @param view 根view
*/
private void initContacts(View view){
mRecyclerView = view.findViewById(R.id.recyclercontactlistId);
mSideBar = view.findViewById(R.id.sidebar);
mContactsAdapter = new ContactsAdapter(initTestData());
LinearLayoutManager verticalLayoutManager = new LinearLayoutManager(this.getContext());
mRecyclerView.setLayoutManager(verticalLayoutManager);
mRecyclerView.setAdapter(mContactsAdapter);
}
/**
* 初始化測試數據
* @return
*/
private List<ContactData> initTestData(){
List<ContactData> contactData = new ArrayList<>();
contactData.add(new ContactData("alibaba", "123456", 1));
contactData.add(new ContactData("tencent", "654321", 2));
contactData.add(new ContactData("spaceon", "999999", 3));
contactData.add(new ContactData("666", "0000", 4));
contactData.add(new ContactData("老黃", "18381680077", 0));
contactData.add(new ContactData("ligj", "1550805", 9));
return contactData;
}
}
稍微等一下,下午繼續完善。
三,結果圖