android中带索引的列表-----索引的高级使用
在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 有需求的朋友可以评价 谢谢 感谢支持 期待共同进步
上一篇: 什么是SOA?