這個最近在做一個相關的通訊錄索引顯示,網上大都是手機豎屏的顯示但是對於平板的橫屏顯示的基本上沒有。但是原理基本上是一直的,就是自定義View一個索引控件,這裏的改編自網上的一個手機通訊錄索引項目。本身這裏的demo也是改編於它的項目。
該demo實現的相關相關功能,水平佈局顯示相關聯繫人頭像和名字。添加每個項目的首字母顯示。點擊下方索引跳轉到對應的聯繫人部分。下方索引可以隨滑動顯示並變更佈局。由於是demo項目相關編碼比較隨便。
這個就是效果圖。
這裏涉及到對象是有首字母拼音的,這裏採用相關的tinypinyin 架構相關的聯繫人對象可以自己處理,這裏的對象中要包含相關的首字母並涉及 到相關是否顯示。
CharLoadIndexView 這個就是下方的底部索引自定義view。而CharIndexView這個則是相關的右部側索引。
相關的構造方法和初始化就不介紹了,可以看代碼。這裏從相關的OnLayout方法介紹。
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
Paint.FontMetrics fm = textPaint.getFontMetrics();
float textHeight = fm.bottom - fm.top;
int width = getWidth() -getPaddingLeft() - getPaddingRight();
itemWeight = width/(float)CHARS.length;
textY = textHeight;
textX = itemWeight;
這裏對於每個索引字項找到對應的高度和寬度。這裏需要注意的是,底部排列則是屏幕寬度除相關的索引數組長度,側邊排列則是高度除以長度。
具體的繪製方法則是在onDraw()方法中:
@Override
protected void onDraw(Canvas canvas) {
float centerX = getPaddingLeft() +textX/2.0f;
float centerY =getPaddingTop() + textY/2.0f;
if (centerX <= 0 || centerY <= 0) return;
for (int i = 0; i < CHARS.length; i++) {
char c = CHARS[i];
textPaint.setColor(i == currentIndex ? indexTextColor : textColor);
canvas.drawText(String.valueOf(c), centerX, centerY, textPaint);
centerX += itemWeight;
}
}
由於是從左邊和頂部開始繪製。則需要考慮相關的位置,這裏座標中心,可以計算出爲相對應的高度的1/2,至於爲什麼看座標參考系應該明白。這裏由於是底部顯示,則繪製完成一個字段之後,相關的橫座標X則增加一個單字段的寬度。
這裏相關的字體顏色設置 ,可以自己根據實際情況變化,這裏下方的觸摸事件就改變相關的位置,從而達到改變對應的索引顏色。
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.d(TAG, "onTouchEvent: " + event.getAction());
int currentIndex = INDEX_NONE;
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
setBackgroundDrawable(indexDrawable);
currentIndex = computeCurrentIndex(event);
if (listener != null) {
listener.onCharIndexSelected(String.valueOf(CHARS[currentIndex]));
}
break;
case MotionEvent.ACTION_MOVE:
currentIndex = computeCurrentIndex(event);
if (listener != null) {
listener.onCharIndexSelected(String.valueOf(CHARS[currentIndex]));
}
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
setBackgroundDrawable(null);
if (listener != null) {
listener.onCharIndexSelected(null);
}
break;
}
if (currentIndex != this.currentIndex) {
this.currentIndex = currentIndex; //這控制相關的點擊的字母顏色
invalidate();
if (this.currentIndex != INDEX_NONE && listener != null) {
listener.onCharIndexChanged(CHARS[this.currentIndex]);
}
}
return true;
}
觸摸事件主要是觸摸相關的監聽的接口事件以便於註冊接口的地方調用,以及改變對應的下方索引顏色。
private int computeCurrentIndex(MotionEvent event) {
if (itemWeight <= 0) return INDEX_NONE;
float x = event.getX() - getPaddingLeft();
int index = (int) (x / itemWeight);
if (index < 0) {
index = 0;
} else if (index >= CHARS.length) {
index = CHARS.length - 1;
}
return index;
}
這裏根據事件得到相關的索引位置,如果是側邊則應該使用相關的高度Y值來做判斷。
當然還有一個重要的功能就是在相關的每個首字母第一項顯示對應的字母分組。這裏採用了網上的方案就是使用分割線。
通過查看官方文檔,:addItemDecoration(RecyclerView.ItemDecoration decor)這個方法設置分隔線RecyclerView.ItemDecoration是一個類,裏面封裝了三個方法:
(1)void getItemOffsets ()
(2)void onDraw ()
(3)void onDrawOver ()
通過上面的三個方法,可以看出,這是要自己直接畫上去,準確的說這幾個方法是:添加Divider,主要是找到添加Divider的位置, 而Divider是在drawable文件中寫好了的。 利用onDraw和onDrawOver都差不多,我們在創建自己的Decoration類繼承RecyclerView.ItemDecoration的時候,我們只要重寫getItemOffsets(),還有onDraw()和onDrawOver兩者其中之一就可以了.
那getItemOffsets()方法有什麼用呢?從字面意思就是Item要偏移, 由於我們在Item和Item之間加入了分隔線,線其實本質就是一個長方形,也是用戶自定義的,既然線也有長寬高,就畫橫線來說,上面的Item加入了分隔線,那下面的Item就要往下平移,平移的量就是分隔線的高度。
我們要確定:分隔線的left, top, right, Bottom. 在Adapter中,我們很容易通過parent(這個parent它其實就是我們能看到的部分)獲取每一個childView:
(1)left:parent.getPaddingLeft()
(2)right: parent. getWidth()-parent.getPaddingRight();
(3)top : 就是紅線的上面:我們通過ChildView.getBottom()來得到這個Item的底部的高度,也就是藍線位置,藍線和紅線之間間距:就是這個Item佈局文件的:layout_marginBottom, 然後top的位置就是兩者之和。
(4)bttom: 就是top加上分隔線的高度:top+線高
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State
state) {
int position = parent.getChildAdapterPosition(view);
int headerHeight = 0;
if (position != RecyclerView.NO_POSITION
&& hasHeader(position) && shouldShowHeader(position)) {
headerHeight = getHeader(parent, position).itemView.getHeight();
}
outRect.set(0, headerHeight, 0, 0);
}
這個就是測算每個item應該偏移的位置。
private long firstHeaderId = 0; //每次的的真正第一行顯示
/**
* {@inheritDoc}
*/
@Override
public void onDrawOver(Canvas canvas, RecyclerView parent, RecyclerView.State state) {
final int count = parent.getChildCount();
for (int layoutPos = 0; layoutPos < count; layoutPos++) {
final View child = parent.getChildAt(layoutPos);
final int adapterPos = parent.getChildAdapterPosition(child);
if (adapterPos != RecyclerView.NO_POSITION && hasHeader(adapterPos)) {
long headerId = mAdapter.getHeaderId(adapterPos);
if (headerId != previousHeaderId ) {
View header = getHeader(parent, adapterPos).itemView;
canvas.save();
final int left = child.getLeft();
final int top = getHeaderTop(parent, child, header, adapterPos, layoutPos);
canvas.translate(left, top);
header.setTranslationX(left);
header.setTranslationY(top);
header.draw(canvas);
canvas.restore();
previousHeaderId = headerId;
}
}
}
}
這裏怎麼控制每次的header是否顯示呢,當每次繪製的時候如果這個頭部和索引的記錄值不相等就繪製一次。
怎個Demo就介紹到這裏,這個demo中還使用到了相關的Rxjava2 處理對應的數據獲取和顯示數據的線程切換。
相關項目源碼demo地址:https://github.com/tangrunfa/BotIndexContact