android中带索引的列表-----索引的高级使用
来源:互联网 发布:网约车平台软件开发 编辑:程序博客网 时间:2024/06/01 09:15
在Android中索引无处不在 比如通讯录 方便检索信息的展示页等
下面来介绍一个带索引检索的简约实用的list,首先来看效果图:
由于csdn 上传gif限制 图片略失真;
在这个demo中主要实现了如下功能:
1.在listView快速滑动时 检索索引在右侧出现,当滑动结束 ,不触摸索引条,3s后 索引渐变消失
2.listView随着索引的滑动点击 滑动到相应位置 如果是拼音 建议导入拼音包来检索
3.在索引条上滑动时候,屏幕中间 会有灰色来显示检索的关键字
1 初始化数据
接下来看代码 详解代码逻辑:
首先看MainActivity,这是比较简单的代码,只展示:
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); initView(); initData(); initEvent(); } private void initData() { mItems = new ArrayList<String>(); mItems.add("A 想念这过去的朋友 我们的爱变成期待"); mItems.add("Steve Jobs"); mItems.add("Inheritance (The Inheritance Cycle)"); mItems.add("17/08/15 Good Moment"); mItems.add("Title Is Mine"); mItems.add("Book For My Firend"); mItems.add("E 北京的夏晚 独自走在安河桥北"); mItems.add("C 人潮人落 加班撸代码到深夜程序员的痛"); mItems.add("Every Body I M Super Soul"); mItems.add("Death Comes to Pemberley"); mItems.add("B OK 笨鸟当先飞 亦当迟回"); mItems.add("Steve Jobs"); mItems.add("Inheritance (The Inheritance Cycle)"); mItems.add("11/22/63: A Novel"); mItems.add("The Hunger Games"); mItems.add("TC 加油 希望未来会感谢现在的自己"); mItems.add("Explosive Eighteen: A Stephanie Plum Novel"); mItems.add("Catching Fire (The Second Book of the Hunger Games)"); mItems.add("Elder Scrolls V: Skyrim: Prima Official Game Guide"); mItems.add("Death Comes to Pemberley"); //对集合进行排序 Collections.sort(mItems); } private void initEvent() { SectionAdapter adapter = new SectionAdapter(this, android.R.layout.simple_list_item_1,mItems); mList.setAdapter(adapter); mList.setFastScrollEnabled(true); } private void initView() { mList = (ListView) findViewById(R.id.listView); }
2 初始化Adapter 实现 SectionIndexer
下面来看 Adaper 这里impletement SectionIndexer ,完成索引条数据的初始化和索引和listViewItem的匹配逻辑
public class SectionAdapter extends ArrayAdapter<String> implements SectionIndexer{ private String mSection = "#ABCDEFGHIJKLMNOPQRSTUVWXYZ"; public SectionAdapter(@NonNull Context context, @LayoutRes int resource, @NonNull List<String> objects) { super(context, resource, objects); } //获取全部索引 @Override public Object[] getSections() { String[] section = new String[mSection.length()]; for (int i = 0; i < mSection.length(); i++) { section[i] = String.valueOf(mSection.charAt(i)); } return section; } @Override public int getPositionForSection(int section) { for (int i = section; i >= 0; i--) { for (int j = 0; j < getCount(); j++) { if (i == 0){ for (int k = 0; k < 9; k++) { if (StringMatcher.match(String.valueOf(getItem(j).charAt(0)),String.valueOf(k))) return j; } }else { if (StringMatcher.match(String.valueOf(getItem(j).charAt(0)), String.valueOf(mSection.charAt(i)))) return j; } } } return 0; } @Override public int getSectionForPosition(int pos) { return 0; } }}
在这里主要看两个方法:getSections()和getPositionForSection(int section);在getSection中完成索引集合的填充和返回(详细看上一块代码);
在这里我们主要看下getPositionForSection(int section):
@Override public int getPositionForSection(int section) { for (int i = section; i >= 0; i--) { for (int j = 0; j < getCount(); j++) { if (i == 0){ for (int k = 0; k < 9; k++) { if (StringMatcher.match(String.valueOf(getItem(j).charAt(0)),String.valueOf(k))) return j; } }else { if (StringMatcher.match(String.valueOf(getItem(j).charAt(0)), String.valueOf(mSection.charAt(i)))) return j; } } } return 0; }
这里双重循环,索引条中”#”和”A~Z”分别匹配ListView首字母为数字和A~Z的字母,在这个demo中略有局限,有需求 可以单独导入拼音包;殊途同归,下面 看匹配算法StringMatcher.match:就是一个java的API:
public static boolean match(String text,String keyword){ return text.contains(keyword) ; }
有兴趣的可以自己手写实现,不同我觉得没必要 , 毕竟都是有经验的程序员;
3
接着我们看自定义的listView:
@Override public boolean isFastScrollEnabled() { return mIsFast; } //设置快速滑动 @Override public void setFastScrollEnabled(boolean enabled) { //super.setFastScrollEnabled(enabled); mIsFast = enabled; //Log.i("==onDraw",enabled + ""); if (mIsFast){ if (mScroll == null){ mScroll = new IndexScroll(getContext(),this); } }else{ if (mScroll != null) { mScroll.hide(); mScroll = null; } } } @Override public void setAdapter(ListAdapter adapter) { super.setAdapter(adapter); if (mScroll != null){ Log.i("===","setAdapter"); mScroll.setAdapter(adapter); } } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); if (mScroll != null){ //Log.i("===","onDraw"); mScroll.onDraw(canvas); //Log.i("==onDraw","draw"); } } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); if (mScroll != null){ mScroll.onSizeChanged(w,h,oldw,oldh); } } @Override public boolean onTouchEvent(MotionEvent ev) { if (mScroll != null && mScroll.onTouchEvent(ev)){ return true; } //监听手势控制索引条的显隐 if (mGes == null){ mGes = new GestureDetector(getContext(), new GestureDetector.SimpleOnGestureListener() { @Override public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { // If fling happens, index bar shows if (mScroll != null) mScroll.show(); //Log.i("==GestureDetector","showing"); return super.onFling(e1, e2, velocityX, velocityY); } }); } mGes.onTouchEvent(ev); return super.onTouchEvent(ev); } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { //if (mScroll != null){ if (mScroll.containXY(ev.getX(),ev.getY())){ return true; } return super.onInterceptTouchEvent(ev); }
在这些代码中大部分是重写listView的绘制方法和生命周期.当然这些是核心,但是我们一会要单独放在一个类中写,下面会详细讲;所以暂时搁浅,来看自定义ListView中一个关键的逻辑:通过监听手势,listView快速滑动时候控制索引条的显隐(当然不要忘了在MainActivity中初始化ListView时候讲快速滑动设置为true,开篇就又代码,不清楚的可以自己稍微瞅一下):
//是否快速滑动 @Override public boolean isFastScrollEnabled() { return mIsFast; } //设置快速滑动 @Override public void setFastScrollEnabled(boolean enabled) { //super.setFastScrollEnabled(enabled); mIsFast = enabled; //Log.i("==onDraw",enabled + ""); if (mIsFast){ if (mScroll == null){ mScroll = new IndexScroll(getContext(),this); } }else{ if (mScroll != null) { mScroll.hide(); mScroll = null; } } } @Override public boolean onTouchEvent(MotionEvent ev) { if (mScroll != null && mScroll.onTouchEvent(ev)){ return true; } //监听手势控制索引条的显隐 if (mGes == null){ mGes = new GestureDetector(getContext(), new GestureDetector.SimpleOnGestureListener() { @Override public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { // If fling happens, index bar shows if (mScroll != null) mScroll.show(); //Log.i("==GestureDetector","showing"); return super.onFling(e1, e2, velocityX, velocityY); } }); } mGes.onTouchEvent(ev); return super.onTouchEvent(ev); }
这些都是一些固定的Api 相关关键逻辑我代码中有标记有注释,在这里不做解释;
4 核心类的实现
核心类的实现IndexScroll,在我当初写的时候是为了分担自定义listView的代码和逻辑压力,相当于抽取类吧;
ok~我们来一步步看代码:
4.1代码初始化
首先来看初始化:
public void onSizeChanged(int w, int h, int oldw, int oldh) { //Log.i("==onSizeChanged","onSizeChanged"); //开始初始化变量 mDety = (int) mContext.getResources().getDisplayMetrics().density; mScaleDety = (int) mContext.getResources().getDisplayMetrics().scaledDensity; setAdapter(mList.getAdapter()); mState = HIDEN; mListWidth = w; mListHeight = h; mIndexWidth = 20 * mDety; mIndexMagin = 5 * mDety; mIndexTextSIze = 12 * mScaleDety; mCenterPadding = 10 * mScaleDety; mCenterTextSize = 30 * mScaleDety; mIndexHeight = h - 2 * mIndexMagin; mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); mTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG); mCenterSize = 50 * mDety; mIndexbarRect = new RectF(w - mIndexWidth - mIndexMagin,mIndexMagin, w - mIndexMagin, mIndexMagin + mIndexHeight); //mCenterRect = new RectF(mListWidth - ) }
我们把初始化逻辑放在onSizeChanged中可以防止重复调用初始化,占用资源;
接着看setAdapter 为索引条获取数据资源:
public void setAdapter(Adapter adapter) { this.adapter = adapter; mSectionAdapter = (SectionIndexer) adapter; mSections = (String[]) mSectionAdapter.getSections(); }
通过传入实现SectionIndexer的adapter传入来获取索引的数据:
接下来 来看绘制:索引条和索引条数据绘制(暂时mAlph设为0来保证补触发事件不会随意显隐);以及触摸索引条,屏幕中心显示的弹出框:
public void onDraw(Canvas canvas) { //Log.i("==onSizeChanged","onDraw"); if (mState == HIDEN){ return; } //Log.i("===onDraw","draw"); mPaint.setColor(Color.BLACK); //mPaint.setStyle(Paint.Style.FILL); mPaint.setAlpha(65 * mAlph); mPaint.setAntiAlias(true); //开始绘制索引条 canvas.drawRoundRect(mIndexbarRect,5 * mDety, 5 * mDety,mPaint); //绘制索引条的数据 mTextPaint.setColor(Color.WHITE); mTextPaint.setTextSize(mIndexTextSIze); mTextPaint.setAntiAlias(true); //mPaint.setAlpha(255 * mAlph); for (int i = 0; i < mSections.length; i++) { mIndexCell = (mIndexHeight - 2 * mIndexMagin)/mSections.length; int paddingTop = (int) ((mIndexCell + (mTextPaint.ascent() - mTextPaint.descent()))/2); canvas.drawText(mSections[i], mIndexbarRect.left + ((mIndexWidth - mTextPaint.measureText(mSections[i]))/2), mIndexbarRect.top + mIndexMagin + mIndexCell * i + paddingTop - mTextPaint.ascent(),mTextPaint); } if (mSections != null && mSections.length > 0 ){ if (mCenterPos >= 0){ //绘制弹出框 mPaint.setColor(Color.BLACK); mPaint.setAlpha(90); int top = (mListHeight - mCenterSize)/2; int left = (mListWidth - mCenterSize)/2; mCenterRect = new RectF(left,top,left + mCenterSize,top + mCenterSize); canvas.drawRoundRect(mCenterRect,5 * mDety,5 * mDety,mPaint); //绘制弹出框的数据 mTextPaint.setTextSize(mCenterTextSize); //mCenterSize = (int) (mTextPaint.measureText(mSections[mCenterPos]) + mCenterPadding * 2); int l = (int) (left + ( mCenterSize - mTextPaint.measureText(mSections[mCenterPos]))/2); int paddingTop = (int) ((mCenterSize + (mTextPaint.ascent() - mTextPaint.descent()))/2); int t = (int) ((mListHeight - mCenterSize)/2 + paddingTop - mTextPaint.ascent()); canvas.drawText(mSections[mCenterPos],l,t,mTextPaint); } } }
接着来看show()和hide()方法 这样比较r容易理解 ,毕竟上一层的自定义listView的手势监听是通过indexScroll的show和hide来实现:
public void show() { if (mState == HIDEN){ Log.i("==show","HIDEN...."); setState(SHOWING); } } public void hide() { if (mState == SHOWN) setState(HIDEING); }
接着看setState方法:
public void setState(int state){ if (state < HIDEN || state > HIDEING) return; mState = state; switch (mState){ case SHOWN: mHandler.removeMessages(0); //Log.i("==mHandler",SHOWN + ""); break; case SHOWING: mAlph = 0; start(0); //Log.i("==mHandler",SHOWING + ""); break; case HIDEN: mHandler.removeMessages(0); //Log.i("==mHandler",HIDEN + ""); break; case HIDEING: mAlph = 1; start(3000); //Log.i("==mHandler",HIDEING + ""); break; } }
当mState = SHOWING ,则会设置mAlph = 0 通过handler来线程重复调用刷新,当mAlph = 1时设置为mState = SHOWN 若索引条没有触摸,则开线程将mState == HIDEIND,然后mAlph = 1;延迟3s后开始刷新绘制 逐渐隐藏索引条,最后mAlph = 0 ,mState = HIDEN;注意当mState = SHOWN 和mState = HIDEN时要移除线程;
那么 瞄一眼线程开启的逻辑:
private void start(int delay) { mHandler.removeMessages(0); mHandler.sendEmptyMessageAtTime(0, SystemClock.uptimeMillis() + delay); } private Handler mHandler = new Handler(){ @Override public void handleMessage(Message msg) { super.handleMessage(msg); switch (mState){ case SHOWING: mAlph += (1 - mAlph)*0.2; //Log.i("==mHandler",mAlph + ""); if (mAlph > 0.9){ mAlph = 1; setState(SHOWN); } mList.invalidate(); start(20); break; case SHOWN: setState(HIDEING); break; case HIDEING: mAlph -= mAlph * 0.2; //Log.i("==HIDEING","HIDEING..."); if (mAlph <0.1){ mAlph = 0; setState(HIDEN); } mList.invalidate(); start(10); break; } } };
接着来看手指在索引条中滑动逻辑,其实就是通过x,y坐标限制来拦截listView的滑动:
public boolean onTouchEvent(MotionEvent ev) { int action = ev.getAction(); switch (action){ case MotionEvent.ACTION_DOWN: if (mState != HIDEN && containXY(ev.getX(),ev.getY())){ setState(SHOWN); int pos = getPos(ev.getY()); //mList.setSelection(pos); mCenterPos = pos; int positionForSection = mSectionAdapter.getPositionForSection(pos); mList.setSelection(positionForSection); //mList.setSelection(.getPositionForSection(mCurrentSection)); return true; } case MotionEvent.ACTION_MOVE: if (containXY(ev.getX(),ev.getY())){ int pos = getPos(ev.getY()); int positionForSection = mSectionAdapter.getPositionForSection(pos); mList.setSelection(positionForSection); mCenterPos = pos; return true; } case MotionEvent.ACTION_UP: setState(HIDEING); mCenterPos = -1; break; } return false; }
以上代码就是手指在索引条中滑动的触摸事件逻辑;其也是根据mState状态来控制setState来通过线程完成一部分逻辑;
ok~ 到这里 代码就详尽的解释完了 过段时间代码回上传github 有需求的朋友可以评价 谢谢 感谢支持 期待共同进步
- android中带索引的列表-----索引的高级使用
- android之带右侧字母(拼音)索引的列表
- android之带右侧字母(拼音)索引的列表
- react native 带索引的城市列表
- react native 带索引的城市列表
- react native 带索引的城市列表
- Android带索引联系人列表
- Android数据库Sqlite中索引的使用
- Android数据库Sqlite中索引的使用
- Android的高级特效—索引
- 带索引的HIVE
- .带索引的mapReduce
- 带索引的mapReduce
- 带索引的HIVE
- 带索引的mapReduce
- android带索引和标题的listview
- Android 带字母索引的侧边栏
- 带中文索引的ListView 仿微信联系人列表
- Activity使用主题不兼容报错-You need to use a Theme.AppCompat theme (or descendant) with this activity
- uva 10603 Fill code2
- 网站开发(十二)前台栏目模块分配
- pxe装系统简易原理及配置
- Faster\Slower 快慢指针的应用
- android中带索引的列表-----索引的高级使用
- NYOJ 49 开心的小明(01背包)
- .net reflector 反编译失败 索引超出了数组界限问题处理方法
- JAVA中常用的Map和Collection数据结构图解
- SQUASHFS error 解决
- 博弈论--从 必胜点与必败点 到 SG 函数
- hdu3452 最小割
- ACM训练日记—8月17日
- windows c++使用hiredis同步模式实现发布订阅