标签:
项目需要,今天学习了一下索引
涉及到的技术:
绘制右侧的索引条
点击某个字母,定位到ListView控件的指定位置
布局文件:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent"> <com.example.suoyin.IndexableListView android:layout_width="fill_parent" android:layout_height="fill_parent" android:id="@+id/listview" /> </LinearLayout>
package com.example.suoyin; import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.RectF; import android.os.Handler; import android.os.Message; import android.os.SystemClock; import android.view.MotionEvent; import android.widget.Adapter; import android.widget.ListView; import android.widget.SectionIndexer; /** * 右侧的索引条 * * @author by 佚名 * */ public class IndexScroller { private float mIndexbarWidth; // 索引条宽度 private float mIndexbarMargin; // 索引条外边距 private float mPreviewPadding; // private float mDensity; // 密度 private float mScaledDensity; // 缩放密度 private float mAlphaRate; // 透明度 private int mState = STATE_HIDDEN; // 状态 private int mListViewWidth; // ListView宽度 private int mListViewHeight; // ListView高度 private int mCurrentSection = -1; // 当前部分 private boolean mIsIndexing = false; // 是否正在索引 private ListView mListView = null; private SectionIndexer mIndexer = null; private String[] mSections = null; private RectF mIndexbarRect; // 4种状态(已隐藏、正在显示、已显示、正在隐藏) private static final int STATE_HIDDEN = 0; private static final int STATE_SHOWING = 1; private static final int STATE_SHOWN = 2; private static final int STATE_HIDING = 3; public IndexScroller(Context context, ListView lv) { mDensity = context.getResources().getDisplayMetrics().density; mScaledDensity = context.getResources().getDisplayMetrics().scaledDensity; mListView = lv; setAdapter(mListView.getAdapter()); mIndexbarWidth = 20 * mDensity; // 索引条宽度 mIndexbarMargin = 10 * mDensity;// 索引条间距 mPreviewPadding = 5 * mDensity; // 内边距 } public void draw(Canvas canvas) { if (mState == STATE_HIDDEN) return; // mAlphaRate determines the rate of opacity Paint indexbarPaint = new Paint(); indexbarPaint.setColor(Color.BLACK); indexbarPaint.setAlpha((int) (64 * mAlphaRate)); indexbarPaint.setAntiAlias(true); // 画右侧字母索引的圆矩形 canvas.drawRoundRect(mIndexbarRect, 5 * mDensity, 5 * mDensity, indexbarPaint); if (mSections != null && mSections.length > 0) { // Preview is shown when mCurrentSection is set if (mCurrentSection >= 0) { Paint previewPaint = new Paint(); // 用来绘画所以条背景的画笔 previewPaint.setColor(Color.BLACK);// 设置画笔颜色为黑色 previewPaint.setAlpha(96); // 设置透明度 previewPaint.setAntiAlias(true);// 设置抗锯齿 previewPaint.setShadowLayer(3, 0, 0, Color.argb(64, 0, 0, 0)); // 设置阴影层 Paint previewTextPaint = new Paint(); // 用来绘画索引字母的画笔 previewTextPaint.setColor(Color.WHITE); // 设置画笔为白色 previewTextPaint.setAntiAlias(true); // 设置抗锯齿 previewTextPaint.setTextSize(50 * mScaledDensity); // 设置字体大小 // 文本的宽度 float previewTextWidth = previewTextPaint .measureText(mSections[mCurrentSection]); float previewSize = 2 * mPreviewPadding + previewTextPaint.descent() - previewTextPaint.ascent(); RectF previewRect = new RectF( (mListViewWidth - previewSize) / 2, (mListViewHeight - previewSize) / 2, (mListViewWidth - previewSize) / 2 + previewSize, (mListViewHeight - previewSize) / 2 + previewSize); // 中间索引的那个框 canvas.drawRoundRect(previewRect, 5 * mDensity, 5 * mDensity, previewPaint); // 绘画索引字母 canvas.drawText( mSections[mCurrentSection], previewRect.left + (previewSize - previewTextWidth) / 2 - 1, previewRect.top + mPreviewPadding - previewTextPaint.ascent() + 1, previewTextPaint); } // 绘画右侧索引条的字母 Paint indexPaint = new Paint(); indexPaint.setColor(Color.WHITE); indexPaint.setAlpha((int) (255 * mAlphaRate)); indexPaint.setAntiAlias(true); indexPaint.setTextSize(12 * mScaledDensity); float sectionHeight = (mIndexbarRect.height() - 2 * mIndexbarMargin) / mSections.length; float paddingTop = (sectionHeight - (indexPaint.descent() - indexPaint .ascent())) / 2; for (int i = 0; i < mSections.length; i++) { float paddingLeft = (mIndexbarWidth - indexPaint .measureText(mSections[i])) / 2; canvas.drawText(mSections[i], mIndexbarRect.left + paddingLeft, mIndexbarRect.top + mIndexbarMargin + sectionHeight * i + paddingTop - indexPaint.ascent(), indexPaint); } } } public boolean onTouchEvent(MotionEvent ev) { switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: // 按下,开始索引 // If down event occurs inside index bar region, start indexing if (mState != STATE_HIDDEN && contains(ev.getX(), ev.getY())) { setState(STATE_SHOWN); // It demonstrates that the motion event started from index bar mIsIndexing = true; // Determine which section the point is in, and move the list to // that section mCurrentSection = getSectionByPoint(ev.getY()); mListView.setSelection(mIndexer .getPositionForSection(mCurrentSection)); return true; } break; case MotionEvent.ACTION_MOVE: // 移动 if (mIsIndexing) { // If this event moves inside index bar if (contains(ev.getX(), ev.getY())) { // Determine which section the point is in, and move the // list to that section mCurrentSection = getSectionByPoint(ev.getY()); mListView.setSelection(mIndexer .getPositionForSection(mCurrentSection)); } return true; } break; case MotionEvent.ACTION_UP: // 抬起 if (mIsIndexing) { mIsIndexing = false; mCurrentSection = -1; } if (mState == STATE_SHOWN) setState(STATE_HIDING); break; } return false; } public void onSizeChanged(int w, int h, int oldw, int oldh) { mListViewWidth = w; mListViewHeight = h; mIndexbarRect = new RectF(w - mIndexbarMargin - mIndexbarWidth, mIndexbarMargin, w - mIndexbarMargin, h - mIndexbarMargin); } // 显示 public void show() { if (mState == STATE_HIDDEN) setState(STATE_SHOWING); else if (mState == STATE_HIDING) setState(STATE_HIDING); } // 隐藏 public void hide() { if (mState == STATE_SHOWN) setState(STATE_HIDING); } public void setAdapter(Adapter adapter) { if (adapter instanceof SectionIndexer) { mIndexer = (SectionIndexer) adapter; mSections = (String[]) mIndexer.getSections(); } } // 设置状态 private void setState(int state) { if (state < STATE_HIDDEN || state > STATE_HIDING) return; mState = state; switch (mState) { case STATE_HIDDEN: // Cancel any fade effect // 取消渐退的效果 mHandler.removeMessages(0); break; case STATE_SHOWING: // Start to fade in // 开始渐进效果 mAlphaRate = 0; fade(0); break; case STATE_SHOWN: // Cancel any fade effect // 取消渐退的效果 mHandler.removeMessages(0); break; case STATE_HIDING: // Start to fade out after three seconds // 隐藏3秒钟 mAlphaRate = 1; fade(3000); break; } } private boolean contains(float x, float y) { // Determine if the point is in index bar region, which includes the // right margin of the bar return (x >= mIndexbarRect.left && y >= mIndexbarRect.top && y <= mIndexbarRect.top + mIndexbarRect.height()); } private int getSectionByPoint(float y) { if (mSections == null || mSections.length == 0) return 0; if (y < mIndexbarRect.top + mIndexbarMargin) return 0; if (y >= mIndexbarRect.top + mIndexbarRect.height() - mIndexbarMargin) return mSections.length - 1; return (int) ((y - mIndexbarRect.top - mIndexbarMargin) / ((mIndexbarRect .height() - 2 * mIndexbarMargin) / mSections.length)); } private void fade(long 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 STATE_SHOWING: // Fade in effect // 淡进效果 mAlphaRate += (1 - mAlphaRate) * 0.2; if (mAlphaRate > 0.9) { mAlphaRate = 1; setState(STATE_SHOWN); } mListView.invalidate(); fade(10); break; case STATE_SHOWN: // If no action, hide automatically setState(STATE_HIDING); break; case STATE_HIDING: // Fade out effect // 淡出效果 mAlphaRate -= mAlphaRate * 0.2; if (mAlphaRate < 0.1) { mAlphaRate = 0; setState(STATE_HIDDEN); } mListView.invalidate(); fade(10); break; } } }; }
package com.example.suoyin; import android.content.Context; import android.graphics.Canvas; import android.util.AttributeSet; import android.view.GestureDetector; import android.view.MotionEvent; import android.widget.ListAdapter; import android.widget.ListView; /** * 自定义索引列表 * * @author by 佚名 * */ public class IndexableListView extends ListView { private boolean mIsFastScrollEnabled = false; private IndexScroller mScroller = null;//绘制索引条 private GestureDetector mGestureDetector = null;//检查上下滑动的手势 public IndexableListView(Context context) { super(context); } public IndexableListView(Context context, AttributeSet attrs) { super(context, attrs); } public IndexableListView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); } @Override public boolean isFastScrollEnabled() { return mIsFastScrollEnabled; } @Override public void setFastScrollEnabled(boolean enabled) { mIsFastScrollEnabled = enabled; if (mIsFastScrollEnabled) { if (mScroller == null) mScroller = new IndexScroller(getContext(), this); } else { if (mScroller != null) { mScroller.hide(); mScroller = null; } } } @Override public void draw(Canvas canvas) { super.draw(canvas); // Overlay index bar if (mScroller != null) mScroller.draw(canvas); } @Override public boolean onTouchEvent(MotionEvent ev) { // Intercept ListView's touch event if (mScroller != null && mScroller.onTouchEvent(ev)) return true; if (mGestureDetector == null) { // 创建一个GestureDetector(手势探测器) mGestureDetector = new GestureDetector(getContext(), new GestureDetector.SimpleOnGestureListener() { @Override public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { // If fling happens, index bar shows // 显示索引条 mScroller.show(); return super.onFling(e1, e2, velocityX, velocityY); } }); } mGestureDetector.onTouchEvent(ev); return super.onTouchEvent(ev); } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { return true; } @Override public void setAdapter(ListAdapter adapter) { super.setAdapter(adapter); if (mScroller != null) mScroller.setAdapter(adapter); } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); if (mScroller != null) mScroller.onSizeChanged(w, h, oldw, oldh); } }
字符串匹配类:将索引条字母与索引列表项进行匹配判断
package com.example.suoyin; public class StringMatcher { // private final static char KOREAN_UNICODE_START = '?'; // 韩文字符编码开始? private final static char KOREAN_UNICODE_END = '?'; // 韩文字符编码结束? private final static char KOREAN_UNIT = '?' - '?'; // 不知道是啥? // 韩文的一些字符初始化 private final static char[] KOREAN_INITIAL = { '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?' }; /** * 字符匹配 * @param value 需要keyword匹配的字符串 list中的文本 * @param keyword #ABCDEFGHIJKLMNOPQRSTUVWXYZ中的一个 * @return 只要value中包含keyword就返回真 */ public static boolean match(String value, String keyword) { if (value == null || keyword == null) return false; if (keyword.length() > value.length()) return false;//在一个小的字符串中查找一个大的字符串肯定找不到 int i = 0, j = 0; do { // 如果是韩文字符并且在韩文初始数组里面 if (isKorean(value.charAt(i)) && isInitialSound(keyword.charAt(j))) { if (keyword.charAt(j) == getInitialSound(value.charAt(i))) { i++; j++; } else if (j > 0) break; else i++; } else { // 逐个字符匹配 if (keyword.charAt(j) == value.charAt(i)) { i++; j++; } else if (j > 0) break; else i++; } } while (i < value.length() && j < keyword.length()); // 如果最后j等于keyword的长度说明匹配成功 return (j == keyword.length()) ? true : false; } // 判断字符是否在韩文字符编码范围内 private static boolean isKorean(char c) { if (c >= KOREAN_UNICODE_START && c <= KOREAN_UNICODE_END) return true; return false; } // 判断是否在韩文字符里面 private static boolean isInitialSound(char c) { for (char i : KOREAN_INITIAL) { if (c == i) return true; } return false; } // 获得韩文初始化字符数组里面的一个字符 private static char getInitialSound(char c) { return KOREAN_INITIAL[(c - KOREAN_UNICODE_START) / KOREAN_UNIT]; } }
package com.example.suoyin; import java.util.ArrayList; import java.util.Collections; import java.util.List; import android.app.Activity; import android.content.Context; import android.os.Bundle; import android.view.Menu; import android.view.MenuItem; import android.widget.ArrayAdapter; import android.widget.SectionIndexer; public class IndexableListViewActivity extends Activity { private ArrayList<String> mItems; private IndexableListView mListView; /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // 初始化一些数据 mItems = new ArrayList<String>(); mItems.add("Diary of a Wimpy Kid 6: Cabin Fever"); mItems.add("Steve Jobs"); mItems.add("Inheritance (The Inheritance Cycle)"); mItems.add("11/22/63: A Novel"); mItems.add("The Hunger Games"); mItems.add("The LEGO Ideas Book"); 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"); mItems.add("Diary of a Wimpy Kid 6: Cabin Fever"); mItems.add("Steve Jobs"); mItems.add("Inheritance (The Inheritance Cycle)"); mItems.add("11/22/63: A Novel"); mItems.add("The Hunger Games"); mItems.add("The LEGO Ideas Book"); 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("做作"); mItems.add("1"); mItems.add("2"); mItems.add("wokao"); Collections.sort(mItems); // 排序 ContentAdapter adapter = new ContentAdapter(this,android.R.layout.simple_list_item_1,mItems); mListView = (IndexableListView) findViewById(R.id.listview); mListView.setAdapter(adapter); mListView.setFastScrollEnabled(true); // 设置快速滑动 } private class ContentAdapter extends ArrayAdapter<String> implements SectionIndexer { private String mSections = "#ABCDEFGHIJKLMNOPQRSTUVWXYZ"; public ContentAdapter(Context context, int textViewResourceId, List<String> objects) { super(context, textViewResourceId, objects); } @Override public int getPositionForSection(int sectionIndex) { /*根据右边索引adbcd...获取左边listView的位置*/ // If there is no item for current section, previous section will be // selected // 如果当前部分没有对应item,则之前的部分将被选择 (比如用户点击索引Y,左边list中没有y开头的,则会选择y之前的x,x也没有就找w,一直往前查,直到遇到第一个有对应item的,否则不进行定位) for (int i = sectionIndex; i >= 0; i--) { for (int j = 0; j < getCount(); j++) { System.out.println(getCount()); if (i == 0) { // # // For numeric section 数字 for (int k = 0; k <= 9; k++) {// 1...9 // 字符串第一个字符与1~9之间的数字进行匹配 if (StringMatcher.match( String.valueOf(getItem(j).charAt(0)), String.valueOf(k))) return j; } } else { // A~Z if (StringMatcher.match( String.valueOf(getItem(j).charAt(0)), String.valueOf(mSections.charAt(i)))) return j; } } } return 0; } @Override public int getSectionForPosition(int position) { return 0; } @Override public Object[] getSections() { String[] sections = new String[mSections.length()]; for (int i = 0; i < mSections.length(); i++) sections[i] = String.valueOf(mSections.charAt(i)); return sections; } } }
标签:
原文地址:http://blog.csdn.net/wei_chong_chong/article/details/52071055