在联系人界面 可以看到这种界面 手指快速滑动右边滑动条时 可以显示相应的字母图标

android里提供了android.widget.SectionIndexer这个接口去实现该效果 可是只能显示字母提示

但我们想实现右图这种效果时 则只能自定义去实现了

先上代码

MainActivity

public class MainActivity extends Activity {

  /*
*
* 1 ListView BaseAdapter DataSource 列表 适配器 数据源
* 巨坑 外面只有包上线性布局才会显示
*
*/ @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main); ListView listView = (ListView) findViewById(R.id.list); List<Model> models = new ArrayList<Model>(); for (int i = 0; i < 400; i++) { Model model = new Model("key" + i, "2012" + i);
models.add(model);
} MyAdapter1 adapter = new MyAdapter1(getApplicationContext(), models); listView.setAdapter(adapter); } }

activity_main

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:demoapp="http://schemas.android.com/apk/res/com.example.mylistview"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical" > <com.example.mylistview.CustomFastScrollView
android:id="@+id/fast_scroll_view"
android:layout_width="wrap_content"
android:layout_height="0px"
android:background="#FFFFFF"
android:layout_weight="1"
demoapp:overlayHeight="40dp"
demoapp:overlayScrollThumbWidth="40dp"
demoapp:overlayTextSize="18dp"
demoapp:overlayWidth="100dp" > <ListView
android:id="@+id/list"
android:layout_width="wrap_content"
android:layout_height="fill_parent" /> </com.example.mylistview.CustomFastScrollView> </LinearLayout>

Model

public class Model {

  private String key; // 列表中显示
private String value; // 提示中显示
private String other; public Model(String key, String value) {
super();
this.key = key;
this.value = value;
} /**
* @return the key
*/
public String getKey() {
return this.key;
} /**
* @param key
* the key to set
*/
public void setKey(String key) {
this.key = key;
} /**
* @return the value
*/
public String getValue() {
return this.value;
} /**
* @param value
* the value to set
*/
public void setValue(String value) {
this.value = value;
} }

MyAdapter1

public class MyAdapter1 extends BaseAdapter implements SectionIndexer{
private static final String TAG = "MyAdapter"; private List<Model> countries;
private Context context;
private SectionIndexer sectionIndexer; public MyAdapter1(Context context, List<Model> countries) { this.context = context;
this.countries = countries; } @Override
public int getCount() {
return countries.size();
} @Override
public Object getItem(int position) {
return null;
} @Override
public long getItemId(int position) {
return position;
} @Override
public View getView(int position, View convertView, ViewGroup parent) { // 创建一个LinearLayout,并向其中添加2个组件
LinearLayout line = new LinearLayout(context);
line.setOrientation(0);
ImageView image = new ImageView(context);
image.setImageResource(R.drawable.ic_launcher);
TextView text = new TextView(context);
text.setText(countries.get(position).getKey());
text.setTextSize(20);
text.setTextColor(Color.RED);
line.addView(image);
line.addView(text);
// 返回LinearLayout实例
return line; } @Override
public Object[] getSections() {
return getSectionIndexer().getSections();
} @Override
public int getPositionForSection(int section) {
return getSectionIndexer().getPositionForSection(section);
} @Override
public int getSectionForPosition(int position) {
return getSectionIndexer().getSectionForPosition(position);
} private SectionIndexer getSectionIndexer() {
if (sectionIndexer == null) {
sectionIndexer = createSectionIndexer(countries);
}
return sectionIndexer;
} private SectionIndexer createSectionIndexer(List<Model> countries) { return createSectionIndexer(countries, new Function<Model, String>() {
@Override
public String apply(Model input) {
// show just the first letter in the title
//return input.getName().substring(0, 1); return input.getValue();
}
});
} private SectionIndexer createSectionIndexer(List<Model> countries, Function<Model, String> sectionFunction) { List<String> sections = new ArrayList<String>();
final List<Integer> sectionsToPositions = new ArrayList<Integer>();
final List<Integer> positionsToSections = new ArrayList<Integer>(); for (int i = 0; i < countries.size(); i++) {
Model country = countries.get(i);
String section = sectionFunction.apply(country); Log.i("TAG1", "section----" + section); if (sections.isEmpty() || !sections.get(sections.size() - 1).equals(section)) {
sections.add(section);
sectionsToPositions.add(i);
} positionsToSections.add(sections.size() - 1);
} final String[] sectionsArray = sections.toArray(new String[sections.size()]); return new SectionIndexer() { @Override
public Object[] getSections() {
return sectionsArray;
} @Override
public int getSectionForPosition(int position) {
return positionsToSections.get(position);
} @Override
public int getPositionForSection(int section) {
return sectionsToPositions.get(section);
}
};
} }

Function

public interface Function<E,T> {

    public T apply(E input);

}

最后最关键的类

CustomFastScrollView

public class CustomFastScrollView extends FrameLayout
implements OnScrollListener, OnHierarchyChangeListener { // how much transparency to use for the fast scroll thumb
private static final int ALPHA_MAX = 255; // how long before the fast scroll thumb disappears
private static final long FADE_DURATION = 200; private Drawable mCurrentThumb; //滑动条
private Drawable mOverlayDrawable; //提示文字背景框 private int mThumbH;
private int mThumbW;
private int mThumbY; private RectF mOverlayPos; // custom values I defined
private int mOverlayWidth;
private int mOverlayHeight;
private float mOverlayTextSize;
private int mOverlayScrollThumbWidth; private boolean mDragging;
private ListView mList;
private boolean mScrollCompleted;
private boolean mThumbVisible;
private int mVisibleItem;
private Paint mPaint;
private int mListOffset; private Object [] mSections;
private String mSectionText;
private boolean mDrawOverlay;
private ScrollFade mScrollFade; private Handler mHandler = new Handler(); private BaseAdapter mListAdapter; private boolean mChangedBounds; public static interface SectionIndexer {
Object[] getSections(); int getPositionForSection(int section); int getSectionForPosition(int position);
} public CustomFastScrollView(Context context) {
super(context); init(context, null);
} public CustomFastScrollView(Context context, AttributeSet attrs) {
super(context, attrs); init(context, attrs);
} public CustomFastScrollView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle); init(context, attrs);
} private void useThumbDrawable(Drawable drawable) {
mCurrentThumb = drawable;
mThumbW = mOverlayScrollThumbWidth;//mCurrentThumb.getIntrinsicWidth();
mThumbH = mCurrentThumb.getIntrinsicHeight();
mChangedBounds = true;
} private void init(Context context, AttributeSet attrs) { // set all attributes from xml
if (attrs != null) {
TypedArray typedArray = context.obtainStyledAttributes(attrs,
R.styleable.CustomFastScrollView);
mOverlayHeight = typedArray.getDimensionPixelSize(
R.styleable.CustomFastScrollView_overlayHeight, 0);
mOverlayWidth = typedArray.getDimensionPixelSize(
R.styleable.CustomFastScrollView_overlayWidth, 0);
mOverlayTextSize = typedArray.getDimensionPixelSize(
R.styleable.CustomFastScrollView_overlayTextSize, 0);
mOverlayScrollThumbWidth = typedArray.getDimensionPixelSize(
R.styleable.CustomFastScrollView_overlayScrollThumbWidth, 0); } // Get both the scrollbar states drawables
final Resources res = context.getResources();
//Drawable thumbDrawable = res.getDrawable(R.drawable.scrollbar_handle_accelerated_anim2);
Drawable thumbDrawable = res.getDrawable(R.drawable.bar1);
useThumbDrawable(thumbDrawable); //mOverlayDrawable = res.getDrawable(android.R.drawable.alert_dark_frame);
mOverlayDrawable = res.getDrawable(R.drawable.bar2); mScrollCompleted = true;
setWillNotDraw(false); // Need to know when the ListView is added
setOnHierarchyChangeListener(this); mOverlayPos = new RectF();
mScrollFade = new ScrollFade();
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setTextAlign(Paint.Align.CENTER);
mPaint.setTextSize(mOverlayTextSize);
mPaint.setColor(0xFFFFFFFF);
mPaint.setStyle(Paint.Style.FILL_AND_STROKE);
} private void removeThumb() {
mThumbVisible = false;
// Draw one last time to remove thumb
invalidate();
} @Override
public void draw(Canvas canvas) {
super.draw(canvas); if (!mThumbVisible) {
// No need to draw the rest
return;
} final int y = mThumbY;
final int viewWidth = getWidth();
final CustomFastScrollView.ScrollFade scrollFade = mScrollFade; int alpha = -1;
if (scrollFade.mStarted) {
alpha = scrollFade.getAlpha();
if (alpha < ALPHA_MAX / 2) {
mCurrentThumb.setAlpha(alpha * 2);
}
int left = viewWidth - (mThumbW * alpha) / ALPHA_MAX;
mCurrentThumb.setBounds(left, 0, viewWidth, mThumbH); mChangedBounds = true;
} canvas.translate(0, y);
Log.i("TAG", "left y上的变化 draw" + y);
mCurrentThumb.draw(canvas); //canvas.translate(0, -y);
Log.i("TAG", "left y上的变化 draw" + y); // If user is dragging the scroll bar, draw the alphabet overlay
if (mDragging && mDrawOverlay) {
mOverlayDrawable.draw(canvas);
final Paint paint = mPaint;
float descent = paint.descent();
final RectF rectF = mOverlayPos;
canvas.drawText(mSectionText + "美丽" , (int) (rectF.left + rectF.right) / 2, (int) (rectF.bottom + rectF.top) / 2 + descent, paint);
} else if (alpha == 0) {
scrollFade.mStarted = false;
removeThumb();
} else {
invalidate(viewWidth - mThumbW, y, viewWidth, y + mThumbH);
}
} @Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
if (mCurrentThumb != null) {
mCurrentThumb.setBounds(w - mThumbW, 0, w, mThumbH);
}
final RectF pos = mOverlayPos;
//pos.left = (w - mOverlayWidth) / 2;
pos.left = w-mOverlayWidth-mThumbW;
pos.right = pos.left + mOverlayWidth;
//pos.top = h / 10; // 10% from top
pos.top = 0 - mOverlayHeight/2 + mThumbH/2;
pos.bottom = pos.top + mOverlayHeight;
mOverlayDrawable.setBounds((int) pos.left, (int) pos.top,
(int) pos.right, (int) pos.bottom);
} public void onScrollStateChanged(AbsListView view, int scrollState) {
} public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,
int totalItemCount) { if (totalItemCount - visibleItemCount > 0 && !mDragging) {
mThumbY = ((getHeight() - mThumbH) * firstVisibleItem) / (totalItemCount - visibleItemCount);
if (mChangedBounds) {
final int viewWidth = getWidth();
mCurrentThumb.setBounds(viewWidth - mThumbW, 0, viewWidth, mThumbH);
mChangedBounds = false;
}
}
mScrollCompleted = true;
if (firstVisibleItem == mVisibleItem) {
return;
}
mVisibleItem = firstVisibleItem;
if (!mThumbVisible || mScrollFade.mStarted) {
mThumbVisible = true;
mCurrentThumb.setAlpha(ALPHA_MAX);
}
mHandler.removeCallbacks(mScrollFade);
mScrollFade.mStarted = false;
if (!mDragging) {
mHandler.postDelayed(mScrollFade, 1500);
}
} private void getSections() {
Adapter adapter = mList.getAdapter();
if (adapter instanceof HeaderViewListAdapter) {
mListOffset = ((HeaderViewListAdapter)adapter).getHeadersCount();
adapter = ((HeaderViewListAdapter)adapter).getWrappedAdapter();
}
if (adapter instanceof SectionIndexer) {
mListAdapter = (BaseAdapter) adapter;
mSections = ((SectionIndexer) mListAdapter).getSections();
}
} public void onChildViewAdded(View parent, View child) {
if (child instanceof ListView) {
mList = (ListView)child; mList.setOnScrollListener(this);
getSections();
}
} public void onChildViewRemoved(View parent, View child) {
if (child == mList) {
mList = null;
mListAdapter = null;
mSections = null;
}
} @Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
if (mThumbVisible && ev.getAction() == MotionEvent.ACTION_DOWN) {
if (ev.getX() > getWidth() - mThumbW && ev.getY() >= mThumbY &&
ev.getY() <= mThumbY + mThumbH) {
mDragging = true;
return true;
}
}
return false;
} private void scrollTo(float position) {
int count = mList.getCount();
mScrollCompleted = false;
final Object[] sections = mSections;
int sectionIndex;
if (sections != null && sections.length > 1) {
final int nSections = sections.length; int section = (int) (position * nSections);
if (section >= nSections) {
section = nSections - 1;
}
sectionIndex = section;
final SectionIndexer baseAdapter = (SectionIndexer) mListAdapter;
int index = baseAdapter.getPositionForSection(section); // Given the expected section and index, the following code will
// try to account for missing sections (no names starting with..)
// It will compute the scroll space of surrounding empty sections
// and interpolate the currently visible letter's range across the
// available space, so that there is always some list movement while
// the user moves the thumb.
int nextIndex = count;
int prevIndex = index;
int prevSection = section;
int nextSection = section + 1;
// Assume the next section is unique
if (section < nSections - 1) {
nextIndex = baseAdapter.getPositionForSection(section + 1);
} // Find the previous index if we're slicing the previous section
if (nextIndex == index) {
// Non-existent letter
while (section > 0) {
section--;
prevIndex = baseAdapter.getPositionForSection(section);
if (prevIndex != index) {
prevSection = section;
sectionIndex = section;
break;
}
}
}
// Find the next index, in case the assumed next index is not
// unique. For instance, if there is no P, then request for P's
// position actually returns Q's. So we need to look ahead to make
// sure that there is really a Q at Q's position. If not, move
// further down...
int nextNextSection = nextSection + 1;
while (nextNextSection < nSections &&
baseAdapter.getPositionForSection(nextNextSection) == nextIndex) {
nextNextSection++;
nextSection++;
}
// Compute the beginning and ending scroll range percentage of the
// currently visible letter. This could be equal to or greater than
// (1 / nSections).
float fPrev = (float) prevSection / nSections;
float fNext = (float) nextSection / nSections;
index = prevIndex + (int) ((nextIndex - prevIndex) * (position - fPrev)
/ (fNext - fPrev));
// Don't overflow
if (index > count - 1) index = count - 1; mList.setSelectionFromTop(index + mListOffset, 0);
} else {
int index = (int) (position * count);
mList.setSelectionFromTop(index + mListOffset, 0);
sectionIndex = -1;
} if (sectionIndex >= 0) {
String text = mSectionText = sections[sectionIndex].toString();
mDrawOverlay = (text.length() != 1 || text.charAt(0) != ' ') &&
sectionIndex < sections.length;
} else {
mDrawOverlay = false;
}
} private void cancelFling() {
// Cancel the list fling
MotionEvent cancelFling = MotionEvent.obtain(0, 0, MotionEvent.ACTION_CANCEL, 0, 0, 0);
mList.onTouchEvent(cancelFling);
cancelFling.recycle();
} @Override
public boolean onTouchEvent(MotionEvent me) {
if (me.getAction() == MotionEvent.ACTION_DOWN) {
if (me.getX() > getWidth() - mThumbW
&& me.getY() >= mThumbY
&& me.getY() <= mThumbY + mThumbH) { mDragging = true;
if (mListAdapter == null && mList != null) {
getSections();
} cancelFling();
return true;
}
} else if (me.getAction() == MotionEvent.ACTION_UP) {
if (mDragging) {
mDragging = false;
final Handler handler = mHandler;
handler.removeCallbacks(mScrollFade);
handler.postDelayed(mScrollFade, 1000);
return true;
}
} else if (me.getAction() == MotionEvent.ACTION_MOVE) {
if (mDragging) {
final int viewHeight = getHeight();
mThumbY = (int) me.getY() - mThumbH + 10;
if (mThumbY < 0) {
mThumbY = 0;
} else if (mThumbY + mThumbH > viewHeight) {
mThumbY = viewHeight - mThumbH;
}
// If the previous scrollTo is still pending
if (mScrollCompleted) {
scrollTo((float) mThumbY / (viewHeight - mThumbH));
}
return true;
}
} return super.onTouchEvent(me);
} public class ScrollFade implements Runnable { long mStartTime;
long mFadeDuration;
boolean mStarted; void startFade() {
mFadeDuration = FADE_DURATION;
mStartTime = SystemClock.uptimeMillis();
mStarted = true;
} int getAlpha() {
if (!mStarted) {
return ALPHA_MAX;
}
int alpha;
long now = SystemClock.uptimeMillis();
if (now > mStartTime + mFadeDuration) {
alpha = 0;
} else {
alpha = (int) (ALPHA_MAX - ((now - mStartTime) * ALPHA_MAX) / mFadeDuration);
}
return alpha;
} public void run() {
if (!mStarted) {
startFade();
invalidate();
} if (getAlpha() > 0) {
final int y = mThumbY;
final int viewWidth = getWidth();
invalidate(viewWidth - mThumbW, y, viewWidth, y + mThumbH);
} else {
mStarted = false;
removeThumb();
}
}
} /**
* Call when the list's items have changed
*/
public void listItemsChanged() {
getSections();
}
}

Demo实现的效果如下 下面来分析下代码实现的原理

Demo见

https://github.com/huanyi0723/MyListView/

Android 自定义列表指示器的更多相关文章

  1. Android Launcher分析和修改11——自定义分页指示器(paged_view_indicator)

    Android4.0的Launcher自带了一个简单的分页指示器,就是Hotseat上面那个线段,这个本质上是一个ImageView利用.9.png图片做,效果实在是不太美观,用测试人员的话,太丑了. ...

  2. Android应用--简、美音乐播放器获取专辑图片(自定义列表适配器)

    Android应用--简.美音乐播放器获取专辑图片(自定义列表适配器) 2013年7月3日简.美音乐播放器开发 第二阶段已增加功能: 1.歌词滚动显示 2.来电监听 3.音量控制 4.左右滑动切换歌词 ...

  3. Android中SimpleAdapter的使用—自定义列表

    本人初学Android,今天研究到Adapter这块感觉挺有意思的,写了个自定义列表进行测试 首先我们新建一个layout列表布局文件,具体布局可以自己设定. 下面贴上我的自定义布局文件代码 < ...

  4. Android 自定义 ListView 上下拉动“刷新最新”和“加载更多”歌曲列表

    本文内容 环境 测试数据 项目结构 演示 参考资料 本文演示,上拉刷新最新的歌曲列表,和下拉加载更多的歌曲列表.所谓"刷新最新"和"加载更多"是指日期.演示代码 ...

  5. (转载)Android自定义标签列表控件LabelsView解析

    Android自定义标签列表控件LabelsView解析 作者 donkingliang 关注 2017.03.15 20:59* 字数 759 阅读 406评论 0喜欢 3 无论是在移动端的App, ...

  6. Android零基础入门第39节:ListActivity和自定义列表项

    原文:Android零基础入门第39节:ListActivity和自定义列表项 相信通过前两期的学习,以及会开发最简单的一些列表界面了吧,那么本期接着来学习更多方法技巧. 一.使用ListActivi ...

  7. Android 自定义View合集

    自定义控件学习 https://github.com/GcsSloop/AndroidNote/tree/master/CustomView 小良自定义控件合集 https://github.com/ ...

  8. Android 自定义ListView

    本文讲实现一个自定义列表的Android程序,程序将实现一个使用自定义的适配器(Adapter)绑定 数据,通过contextView.setTag绑定数据有按钮的ListView. 系统显示列表(L ...

  9. [安卓] 16、ListView和GridView结合显示单元实现自定义列表显示效果

    List在各种手机应用中都有体现,是安卓UI设计的必修课. 本文将介绍在开发中如何利用ListView和GridView设计自定义列表. 下面分别是用ListView和GridView做的效果: 上面 ...

随机推荐

  1. 关于viewport的研究

    昨天项目中用到了适应移动端显示的viewport,一般的使用方式如下: <meta name="viewport" content="width=device-wi ...

  2. Spring框架bean的配置(2):SpEL:引用 Bean、属性和方法。。。

    将这些架包放入在工程目录下建立的lib文件夹里,并解压 commons-logging-1.1.1 spring-aop-4.0.0.RELEASE spring-beans-4.0.0.RELEAS ...

  3. Is It A Tree?(并查集)

    Is It A Tree? Time Limit: 1000MS   Memory Limit: 10000K Total Submissions: 26002   Accepted: 8879 De ...

  4. PowerShell调用jira rest api实现jira统计自动化

    通过调用JIRA Rest web api实现统计自动化,首先进行登录模拟: $content = @{username='用户名';password='密码'} $JSON=$content|con ...

  5. Android开发之Theme、Style探索及源码浅析

    1 背景 前段时间群里有伙伴问到了关于Android开发中Theme与Style的问题,当然,这类东西在网上随便一搜一大把模板,所以关于怎么用的问题我想这里也就不做太多的说明了,我们这里把重点放在理解 ...

  6. Activity和Service的生命周期(图)

    1.Activity的生命周期 情形一.一个单独的Activity的正常的生命过程是这样的:onCreate->onStart->onPause->onStop->onDest ...

  7. 2016年10月16日 星期日 --出埃及记 Exodus 18:27

    2016年10月16日 星期日 --出埃及记 Exodus 18:27 Then Moses sent his father-in-law on his way, and Jethro returne ...

  8. 在centos6.3用yum安装redis

    一.centos默认的安装源在官方centos.org上,而redis在第三方的yum源里,所以无法安装,非官方的yum推荐用fedora的epel仓库.当然也可通过配置 /etc/yum.repos ...

  9. mysql DATE_ADD DATE_SUB

    一.DATE_ADD() 函数向日期添加指定的时间间隔. DATE_ADD(date,INTERVAL expr type)date 参数是合法的日期表达式.expr 参数是您希望添加的时间间隔. t ...

  10. Cheatsheet: 2013 09.10 ~ 09.21

    .NET Lucene.Net – Custom Synonym Analyzer Using FiddlerCore to Capture Streaming Audio Immutable col ...