android索引

项目需要,今天学习了一下索引

涉及到的技术:

绘制右侧的索引条

点击某个字母,定位到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];
	}
}

在Activity中使用:

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;
		}
	}
}  
时间: 2024-07-28 18:00:26

android索引的相关文章

Android 自定义View-字母索引表(一)

在有些Android应用中,为了方便快速定位,经常会看到屏幕右侧有一个字母索引表,今天尝试使用自定义View的方式实现了索引表的基本布局. 字母索引表的样式如下面的示意图所示, 此时我们至少需要知道以下几个参数值:1.字母大小;2.单个字母所在区域的宽度;3.单个字母所在区域的高度.现在看如何实现: /** * 26个英文字母以及一个#字符,#字符是为了索引非英文字母的内容,比如电话号码. */ private String[] mAlphabetTable = { "A", &quo

Android 自定义 View 实现通讯录字母索引(仿微信通讯录)

一.效果:我们看到很多软件的通讯录在右侧都有一个字母索引功能,像微信,小米通讯录,QQ,还有美团选择地区等等.这里我截了一张美团选择城市的图片来看看: 我们今天就来实现图片中右侧模块的索引功能,包括触摸显示以选中的索引字母.这里我的UI界面主要是参照微信的界面来实现,所以各位也可以对照微信来看看效果,什么都不说了,只有效果图最具有说服力! 二.分析: 我们看到这样的效果我们心理都回去琢磨,他是如何实现的: 首先,它肯定是通过自定义 View 来实现的,因为 Android 没有提供类似这样的控件

Android带索引联系人列表

网上Android联系人列表的样例也非常多,都和微信的联系人差点儿相同,因为项目用到了联系人列表索引功能(产品把字母item给去掉了),只是也还是好实现.这里我也来分享分享我的实现,免得以后忘了.那先看看效果(Demo在结尾有下载地址): 要达到的效果就是这么简单. 先说说思路吧:首先为联系人对象加入一个pinyin字段,当获取到了联系人原始数据后,把每一个联系人的名字转换为拼音.并为pinyin字段设置值. 然后获取联系人中出现过哪些字母的拼音保存为数组(这就是字母的item),然后和联系人拼

快速集成android实现listview的字母A-Z排序,界面侧边字母索引

 转载请标明出处 Android手机字母A-Z排序侧边索引是非常常见的功能,在此提供快速集成框架.教你用Android studio工具一分钟搞定这个效果. 实现效果: 以及点击F跳转效果 第一步库包导入实现拼音检索功能 -------拼音检索详细见: compile 'com.github.promeg:tinypinyin:1.0.0'// ~80KB同步后后面会下载80k的文件,就可以使用 -------测试一下: public void go1(View view){//按钮go1点击测

浅谈android中手机联系人字母索引表的实现

实际上字母索引表的效果,可以说在现在的众多APP中使用的非常流行,比如支付宝,微信中的联系人,还有购物,买票的APP中选择全国城市,切换城市的时候,这时候的城市也就是按照一个字母索引的顺序来显示,看起来是很方便的.其实这种字母索引表的效果最开始是出现在微信的联系人中.因为觉得这种效果功能在今后的项目中可以说是非常常见,可能会用的上,所以准备来波博客讲述一下实现的原理,一来方便以后自己复习,二来如果能够帮助一些android路上奋斗小伙伴也是蛮有意义的. 下面我们先来看下效果图, 看完效果图后我们

Android之列表索引

其实这个功能是仿苹果的,但是现在大多数Android设备都已经有了这个功能,尤其是在通讯录中最为常见.先来看看今天这个DEMO的效果图(如下图):从图中我们可以看到,屏幕中的主体是一个ListView,右边有一个导航栏,里面放着字母/数字的索引(如图1),用手指点击/移动手指可以改变选中的索引,同时ListView将滚动到选中的索引对应的第一条数据(如图2):如果ListView的数据中没有选中的索引对应的数据,则将ListView滚动到选中索引上面离选中索引最近的有数据的索引对应的第一条数据(

玩转android自定义控件二——自定义索引栏listview

带索引栏的listview,在android开发非常普遍,方便用户进行字母索引,就像微信通讯录这样: 今天,我们就从零到一实现这个具有索引栏的listview. 怎么实现这个控件了,我们应当梳理出一个思路. ①首先应当将字母的索引栏继承与一个控件,通过ondraw方法将字母画出来. ②然后我们应该监听这个字母控件的ontouch事件,来判断用户到底是按了那个字母. ③就是实现这个索引栏与listview的联动,就是将listview滑动到按下字母的位置. 大体流程图如下: 有了前面铺垫,我们引出

Android相关文章索引

开发环境部署 JDK的下载.安装和环境变量配置:http://www.cnblogs.com/duxiuxing/p/4771901.html 通过镜像下载地址安装Android SDK:http://www.cnblogs.com/duxiuxing/p/4666180.html Android Studio开发环境部署:http://www.cnblogs.com/duxiuxing/p/4206051.html Eclipse开发环境部署: Eclipse无法设置NDK路径的解决方法:ht

老猪带你玩转android自定义控件二——自定义索引栏listview

带索引栏的listview,在android开发非常普遍,方便用户进行字母索引,就像微信通讯录这样: 今天,我们就从零到一实现这个具有索引栏的listview. 怎么实现这个控件了,我们应当梳理出一个思路. ①首先应当将字母的索引栏继承与一个控件,通过ondraw方法将字母画出来. ②然后我们应该监听这个字母控件的ontouch事件,来判断用户到底是按了那个字母. ③就是实现这个索引栏与listview的联动,就是将listview滑动到按下字母的位置. 大体流程图如下: 有了前面铺垫,我们引出