校園通訊錄功能,收集校園內的公開聯繫方式等內容,並進行展示。在日常生活中經常可能需要用到。比如 水電處問題了找水電師傅,招生就業辦,學工辦的電話等等。
當然,出於隱私考慮,個人手機號不會被收集展示。只展示辦公室聯繫方式等可在網上或校園內查到的號碼。
前言
校園通訊錄模塊旨在打造便捷的校園信息服務平臺,完善校園信息服務,給校園信息的管理和維護提供更便捷、更安全的服務系統。
(本文對該模塊進行簡易實現,僅講述通訊錄列表樣式的展示)
系列文章
Github地址: 科師有約校園APP
- 手把手帶你擼一個校園APP(一):項目簡介
- 手把手帶你擼一個校園APP(二):應用啓動和歡迎頁面
- 手把手帶你擼一個校園APP(三):用戶模塊(登錄註冊等)
- 手把手帶你擼一個校園APP(四):APP框架及功能設計
- 手把手帶你擼一個校園APP(五):新聞頁面中心模塊
- 手把手帶你擼一個校園APP(六):失物招領&二手交易模塊
- 手把手帶你擼一個校園APP(七):校園文化模塊(社團活動&表白牆&圖說校園)
- 手把手帶你擼一個校園APP(八):校園通訊錄模塊
- 手把手帶你擼一個校園APP(九):課程表模塊(模擬登陸爬取教務處課程信息)
- 手把手帶你擼一個校園APP(十):APP通用模塊(更新,意見反饋等)
實現效果
校園通訊錄:
分析
- 校園通訊錄數據部分比較簡單,總共 姓名稱呼,部門,聯繫方式 3個主要字段即可;
- 通訊錄列表的數據需要排序分組後再顯示;
排序:
按照姓名稱呼的首字母拼音排序
==> 需要獲取首字的拼音
==> 通過三方庫 TinyPinyin 來實現
分組:
可以通過自定義 RecycleView 的 ItemDecoration 來實現,需要額外處理懸浮窗的顯示情況
- 右側顯示字母快速索引列表,並關聯 RecycleView ,可以關聯滑動
通過自定義控件來實現
實現
說明:本模塊的實現主要參考 小馬快跑 的 仿魅族通訊錄 項目,並在其基礎上進行了適合本項目的優化,在此向原作者致敬。
Github:仿魅族通訊錄
作者: 小馬快跑
文章:https://www.jianshu.com/p/7b7b7ee80c44
數據庫設計
字段名 | 描述 | 類型 | 是否主鍵 |
---|---|---|---|
objectId | 唯一標識 | String | 是 |
name | 姓名稱呼 | String | - |
department | 部門 | String | - |
tel | 聯繫方式 | String | - |
安卓實現
下面介紹下校園通訊錄模塊實現的主要思路:
step 1: 數據排序
依賴 TinyPinyin 庫後,對源數據進行加工處理,增加設置 indexTag 字段內容。然後依據該字段進行排序;
public static void sortData(List<Teacher> list) {
if (list == null || list.size() == 0) return;
for (int i = 0; i < list.size(); i++) {
Teacher bean = list.get(i);
String tag = Pinyin.toPinyin(bean.getName().substring(0, 1).charAt(0)).substring(0, 1);
if (tag.matches("[A-Z]")) {
bean.setIndexTag(tag);
} else {
bean.setIndexTag("#");
}
}
Collections.sort(list, new Comparator<Teacher>() {
@Override
public int compare(Teacher o1, Teacher o2) {
if ("#".equals(o1.getIndexTag())) {
return 1;
} else if ("#".equals(o2.getIndexTag())) {
return -1;
} else {
return o1.getIndexTag().compareTo(o2.getIndexTag());
}
}
});
step 2: 數據分組
數據排序完成後,通過自定義 ItemDecoration 來繪製懸浮框及 ItemView 之上的分類Tag
//用來繪製每個ItemView的邊距
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
//............省略部分代碼............
int position = parent.getChildAdapterPosition(view);
if (position == 0) {
//第一條數據有bar
outRect.set(0, dividerHeight, 0, 0);
} else if (position > 0) {
if (TextUtils.isEmpty(mBeans.get(position).getIndexTag())) return;
//與上一條數據中的tag不同時,該顯示bar了
if (!mBeans.get(position).getIndexTag().equals(mBeans.get(position - 1).getIndexTag())) {
outRect.set(0, dividerHeight, 0, 0);
}
}
}
//用來繪製最上面的懸浮框
@Override
public void onDrawOver(Canvas canvas, RecyclerView parent, RecyclerView.State state) {
int position = ((LinearLayoutManager) (parent.getLayoutManager())).findFirstVisibleItemPosition();
final int bottom = parent.getPaddingTop() + dividerHeight;
mPaint.setColor(Color.WHITE);
//繪製懸浮框的範圍
canvas.drawRect(parent.getLeft(), parent.getPaddingTop(), parent.getRight() - parent.getPaddingRight(), parent.getPaddingTop() + dividerHeight, mPaint);
//............省略部分代碼............
mPaint.setTextSize(40);
canvas.drawCircle(DpUtil.dp2px(mContext, 42.5f), bottom - dividerHeight / 2, 35, mPaint);
mPaint.setColor(Color.WHITE);
canvas.drawText(mBeans.get(position).getIndexTag(), DpUtil.dp2px(mContext, 42.5f), bottom - dividerHeight / 3, mPaint);
}
//按需繪製ItemView上面的分類Tag
@Override
public void onDraw(Canvas canvas, RecyclerView parent, RecyclerView.State state) {
//............省略部分代碼............
final int childCount = parent.getChildCount();
for (int i = 0; i < childCount; i++) {
final View child = parent.getChildAt(i);
final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams();
int position = params.getViewLayoutPosition();
if (position == 0) {
//第一條數據有bar
drawTitleBar(canvas, parent, child, mBeans.get(position), tagsStr.indexOf(mBeans.get(position).getIndexTag()));
} else if (position > 0) {
//與上一條數據中的tag不同時,該顯示bar了
if (!mBeans.get(position).getIndexTag().equals(mBeans.get(position - 1).getIndexTag())) {
drawTitleBar(canvas, parent, child, mBeans.get(position), tagsStr.indexOf(mBeans.get(position).getIndexTag()));
} }
}
}
step 3: 自定義右側索引View
- 繪製右側導航欄字母:
@Override
protected void onDraw(Canvas canvas) {
//for循環繪製出所有的導航欄字母
for (int i = 0; i < indexStr.length(); i++) {
String textTag = indexStr.substring(i, i + 1);
float xPos = (mWidth - mPaint.measureText(textTag)) / 2;
canvas.drawText(textTag, xPos, singleHeight * (i + 1) + DpUtil.dp2px(mContext, TOP_MARGIN), mPaint);
}
}
- 處理滑動事件:
在onTouchEvent處理了滑動事件,當手指上下滑動時左側有個圓跟着滑動,這裏用的自定義IndexBar( IndexBar extends ViewGroup,IndexBar包含SideBar )來處理的,當SideBar滑動處於MOVE狀態時通過((IndexBar) getParent()).setDrawData()把一系列位置參數傳到IndexBar中去
@Override
public boolean onTouchEvent(MotionEvent event) {
//處理按下滑動事件
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
//按下
mPaint.setColor(Color.BLACK);
invalidate();
case MotionEvent.ACTION_MOVE:
//滑動 event.getY()得到在父View中的Y座標,通過和總高度的比例再乘以字符個數總長度得到按下的位置
int position = (int) ((event.getY() - getTop() - DpUtil.dp2px(mContext, 80)) / mHeight * indexStr.toCharArray().length);
if (position >= 0 && position < indexStr.length()) {
((IndexBar) getParent()).setDrawData(event.getY(), String.valueOf(indexStr.toCharArray()[position]), position);
if (listener != null) {
listener.indexChanged(indexStr.substring(position, position + 1));
}
}
break;
case MotionEvent.ACTION_UP:
//擡起
((IndexBar) getParent()).setTagStatus(false);
mPaint.setColor(Color.GRAY);
invalidate();
break;
}
return true;
}
主要是在onLayout中把SideBar排列到最右側,並在onDraw中根據SideBar傳過來的一系列位置參數來不斷改變圓的位置,
這裏要注意一下,自定義ViewGroup的onDraw()方法默認是不會調用的,如果想執行onDraw方法,可以通過下面兩種方法:
1.設置透明背景:
在構造函數中:setBackgroundColor(Color.TRANSPARENT);
或者在xml中:android:background="@color/transparent"
2.或者可以在構造函數中添加setWillNotDraw(false);
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int childNum = getChildCount();
if (childNum <= 0) return;
//得到SideBar
View childView = getChildAt(0);
childWidth = childView.getMeasuredWidth();
//把SideBar排列到最右側
childView.layout((mWidth - childWidth), 0, mWidth, mHeight);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (isShowTag) {
//根據位置來不斷變換Paint的顏色
ColorUtil.setPaintColor(mPaint, position);
//繪製圓和文字
canvas.drawCircle((mWidth - childWidth) / 2, centerY, circleRadius, mPaint);
mPaint.setColor(Color.WHITE);
mPaint.setTextSize(80);
canvas.drawText(tag, (mWidth - childWidth - mPaint.measureText(tag)) / 2, centerY - (mPaint.ascent() + mPaint.descent()) / 2, mPaint);
}
}
step 4: 關聯RecycleView
sideBar.setIndexChangeListener(new SideBar.indexChangeListener() {
@Override
public void indexChanged(String tag) {
if (TextUtils.isEmpty(tag) || contactList.size() <= 0) return;
for (int i = 0; i < contactList.size(); i++) {
if (tag.equals(contactList.get(i).getIndexTag())) {
layoutManager.scrollToPositionWithOffset(i, 0);
return;
}
}
}
});
至此,校園通訊錄模塊基本完成。
本文例子中用到多個三方庫TextDrawable,TinyPinyin ,仿魅族通訊錄 等,再次向各位大佬致敬!
如果本文對你有所幫助,還望可以隨手賞一個點贊哈 ~ ~