前言
在我们使用App的过程中,常常会使用到索引,比如在联系人中,如果联系人数量很多,如果仅仅依靠手指的滑动,从上滑到下也是很累的,搜索又需要打字,为了增强用户体验,添加一个索引菜单想必是极好的,滑动侧边就可以快速定位到指定字母开头的联系人了,下面我就来介绍下如何打造一个索引菜单。
打造自己的索引菜单
还是老规矩,先放上效果图,看看是否是你需要的效果。由于是在模拟器上使用鼠标点击,可能录制效果不大好。

侧边栏文字大小自适应
在实际的应用中,我们的侧边栏占用的范围无法确定,所以我们侧边栏上显示的文字需要根据侧边栏当前的大小以及需要显示的字符个数自动调整自己的大小,在自定义View的过程中,我们可以在onMeasure()方法中获取侧边栏占用的大小,从而计算文字大小。
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// 获取父布局传递进来的宽高
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
// 如果是wrap_content,选取默认宽度(30dp)和实际宽度的较小值,否则使用父布局指定的大小
if (widthMode == MeasureSpec.AT_MOST) {
mIndexWidth = Math.min(widthSize, mIndexWidth);
} else {
mIndexWidth = widthSize;
}
// 计算出每一个文字占用的高度,加0.0f的原因是防止精度丢失(小数点后面的位数)
mTextHeight = (heightSize + 0.0f) / mIndex.length;
// 计算出文字的Size,不允许超过索引条的宽度大小
mIndexTextSize = (int) (mTextHeight > mIndexWidth ? mIndexWidth : mTextHeight);
// 设置测量的结果
setMeasuredDimension(
MeasureSpec.makeMeasureSpec(mIndexWidth, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(heightSize, MeasureSpec.EXACTLY));
}
下面我们在布局中使用不同的宽高设置看看是否实现了自适应效果。可以看到,文字能根据侧边栏的大小调整自己的大小。

绘制侧边栏文字
绘制View是在onDraw()方法中的,使用了基本的Canvas绘制方法,如果对Canvas的使用不过关的小伙伴,可以参看我在Android绘图操作以及自定义View贴出来的大神们的博客。
@Override
protected void onDraw(Canvas canvas) {
//如果手指按下了,则绘制背景
if (isTouch) {
canvas.drawColor(TOUCH_BACKGROUP_COLOR);
}
// 设置画笔
mIndexPaint.setTextSize(mIndexTextSize - 2 * mTextPadding);
mIndexPaint.setAntiAlias(true);
mIndexPaint.setStyle(Paint.Style.STROKE);
for (int i = 0; i < mIndex.length; i++) {
if (i != mSelPos) {
mIndexPaint.setColor(TEXT_DEFAULT_COLOR);
} else {
mIndexPaint.setColor(TEXT_TOUCH_COLOR);
}
// 水平居中
float left = (mIndexWidth - mIndexPaint.measureText(mIndex[i])) / 2;
// 垂直居中
float top = i * mTextHeight + mTextPadding;
FontMetrics metrics = mIndexPaint.getFontMetrics();
//因为绘制文字是基于基线位置的,所以需要转换一下
top = top - metrics.ascent;
canvas.drawText(mIndex[i], left, top, mIndexPaint);
}
}
在绘制开始前,先判断是否被点击了,点击情况在ontouch()中获取,然后将上一步计算出来的文字大小设置给Paint,文字的绘制方法: 侧边栏总高度/字符个数 这样可以计算出每一个文字所占用的高度,然后在这个里面居中绘制文字就可以了,这里要注意一点,drawText()方法是基于基线位置的,所以上面转换了一下。可以参看drawText详解这篇博客。
显示文字悬浮窗
当我们点击侧边栏并滑动的时候,就像最上面的gif效果图一样,中间会显示一个窗体显示当前按下的字符,并且当手指离开以后,会有一个延时消失的效果,其实这个悬浮窗就是一个TextView,原理类似于Android桌面悬浮窗,延时我使用的是ValueAnimator,当然你也可以使用新开线程或者使用Timer。悬浮窗也可以选用PopupWindow,设置为不可获取焦点也能实现这个效果。
private void initPopWindow(Context context) {
// mIndexWindow是一个TextView private TextView mIndexWindow;
mIndexWindow = new TextView(context);
mIndexWindow.setLayoutParams(new LayoutParams(
LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
mIndexWindow.setMinWidth(dp2px(90));
mIndexWindow.setBackgroundColor(0x00000000);
mIndexWindow.setGravity(Gravity.CENTER);
mIndexWindow.setTextSize(sp2px(60));
mIndexWindow.setTextColor(TEXT_POPWINDOW_COLOR);
mIndexWindow.setVisibility(View.INVISIBLE);
WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT,
WindowManager.LayoutParams.TYPE_APPLICATION,
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
| WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE,
PixelFormat.TRANSLUCENT);
WindowManager windowManager = (WindowManager) context
.getSystemService(Context.WINDOW_SERVICE);
windowManager.addView(mIndexWindow, lp);
mIndexWindow.setVisibility(View.VISIBLE);
// 这里使用valueAnimator来延时,当结束的时候隐藏悬浮窗
valueAnimator = ValueAnimator.ofFloat(0, 0);
valueAnimator.setDuration(1000);
valueAnimator.addListener(new AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {
}
@Override
public void onAnimationRepeat(Animator animation) {
}
@Override
public void onAnimationEnd(Animator animation) {
mIndexWindow.setVisibility(View.INVISIBLE);
}
@Override
public void onAnimationCancel(Animator animation) {
}
});
}
Touch事件的监听
@Override
public boolean onTouchEvent(MotionEvent event) {
float y = event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
//如果延时1秒还未到,那么取消掉
if (valueAnimator.isRunning()) {
valueAnimator.cancel();
}
//设置为按下状态,绘制背景
isTouch = true;
mSelPos = getTouchPos(y);
break;
case MotionEvent.ACTION_MOVE:
mSelPos = getTouchPos(y);
break;
case MotionEvent.ACTION_UP:
//按下状态取消,不绘制背景
isTouch = false;
mSelPos = -1;
//开始延时效果
valueAnimator.start();
break;
}
postInvalidate();
// 调用回调接口
if (onTouchListener != null && mSelPos >= 0 && mSelPos < mIndex.length) {
onTouchListener.OnIndexTouch(mIndex[mSelPos]);
}
//设置悬浮窗可见并设置悬浮窗文字
if (mSelPos != -1 && isShowPopWindow) {
mIndexWindow.setText(mIndex[mSelPos]);
mIndexWindow.setVisibility(View.VISIBLE);
}
return true;
}
在布局中使用侧边栏
由于悬浮窗还有计算全部是通过代码完成的,所以不依赖其他的文件,将IndexView.java直接复制到工程中即可像普通控件一样使用了。
布局文件
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<com.jay.indexview.IndexView
android:id="@+id/indexview"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_alignParentRight="true" />
</RelativeLayout>
在Activity中添加点击监听事件
package com.jay.indexview;
import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import com.jay.indexview.IndexView.OnIndexTouchListener;
public class MainActivity extends Activity {
private IndexView mIndexView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.fragment_main);
mIndexView = (IndexView) findViewById(R.id.indexview);
mIndexView.setOnIndexTouchListener(new OnIndexTouchListener() {
@Override
public void OnIndexTouch(String sel) {
Log.v("x", "点击的字母是 : " + sel);
}
});
}
}
源码下载 : 360云盘 访问密码 b747
楼主,能否加个好友,有一个小问题。我用java获取我们学校的课表,登录已经没问题了,可是获取课表网页代码时,是因为源码过长还是怎么的,前面没问题,到后面就是乱码了
@王壮 在留言板上有我的QQ,QQ:76031053
楼主这些东西在哪儿学的,有没推荐一下?
@qwer 最上面有推荐资源的标签,里面有很多资源,欢迎补充哦,这个索引控件是因为最近项目需要自己琢磨的