package com.yuyh.library.view.list.indexablelistview;
import android.app.Activity;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.Message;
import android.text.TextUtils;
import android.util.SparseArray;
import android.view.MotionEvent;
import android.view.View;
import android.widget.HeaderViewListAdapter;
import android.widget.ListAdapter;
import android.widget.ListView;
import android.widget.TextView;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
/**
* 字母索引Bar
* Created by YoKeyword on 2016/3/20.
*/
public class IndexBar extends View {
private static final int MSG_SEARCH = 1;
private OnIndexTitleSelectedListener mOnIndexSelectedListener;
private OnSearchResultListener mOnSearchResultListener;
private Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
private Paint focusPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
private ArrayList<String> mIndex = new ArrayList<>(); // List:索引字母
private int mIndexHeight; // 每个索引的高度
private int mSelectionPos; // 记录当前选中位置
private int mCurrentScrollPos; // 记录当前ListView滚动位置
private ListView mListView;
private IndexableAdapter mAdapter;
private List<IndexEntity> mItems;
private List<IndexEntity> mFilterList;
private int mScrollState = -1; // 记录ListView滚动状态
private TextView mOverlayView; // 中心:悬浮 显示索引的View
private TextView mRightOverlayView; // 右侧:悬浮 显示索引的View
private int dp80, mMinHeight;
private HandlerThread mSearchHandlerThread;
private Handler mSearchHandler;
private boolean mNeedShutdown;
public IndexBar(Context context, int barTextColor, int barSelectedColor, float barTextSize) {
super(context);
init(context, barTextColor, barSelectedColor, barTextSize);
}
private void init(Context context, int barTextColor, int barSelectedColor, float barTextSize) {
paint.setColor(barTextColor);
paint.setTextAlign(Paint.Align.CENTER);
paint.setTextSize(barTextSize);
focusPaint.setTextAlign(Paint.Align.CENTER);
focusPaint.setTextSize(barTextSize + 1);
focusPaint.setColor(barSelectedColor);
dp80 = dp2px(context, 80);
mMinHeight = dp2px(context, 32);
}
/**
* 根据Y坐标判断 位置
*/
private int positionForPoint(float y) {
int position = (int) (y / mIndexHeight);
if (position < 0) {
position = 0;
} else if (position > mIndex.size() - 1) {
position = mIndex.size() - 1;
}
return position;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int mode = MeasureSpec.getMode(widthMeasureSpec);
if (mode == MeasureSpec.AT_MOST) {
int maxWidth = dp2px(getContext(), 25);
super.onMeasure(MeasureSpec.makeMeasureSpec(maxWidth, MeasureSpec.EXACTLY), heightMeasureSpec);
return;
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (mIndex.size() == 0) return;
mIndexHeight = getHeight() / mIndex.size();
if (mIndexHeight > mMinHeight) {
mIndexHeight = mMinHeight;
}
for (int i = 0; i < mIndex.size(); i++) {
if (mSelectionPos == i) {
canvas.drawText(mIndex.get(i), getWidth() / 2, mIndexHeight / 2 + mIndexHeight * i, focusPaint);
} else {
canvas.drawText(mIndex.get(i), getWidth() / 2, mIndexHeight / 2 + mIndexHeight * i, paint);
}
}
}
@Override
public boolean onTouchEvent(MotionEvent event) {
float y = event.getY();
int touchPos = positionForPoint(y);
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
processRightOverlayView(y);
if (touchPos != mSelectionPos) {
mSelectionPos = touchPos;
}
if (mListView != null) {
if (mListView.getLastVisiblePosition() == mListView.getCount() - 1) {
invalidate();
}
mListView.setSelection(mAdapter.getIndexMapPosition(mSelectionPos) + mListView.getHeaderViewsCount());
}
processOverlayView(touchPos);
break;
case MotionEvent.ACTION_MOVE:
processRightOverlayView(y);
if (touchPos != mSelectionPos) {
mSelectionPos = touchPos;
if (mListView != null) {
if (mListView.getLastVisiblePosition() == mListView.getCount() - 1) {
invalidate();
}
mListView.setSelection(mAdapter.getIndexMapPosition(mSelectionPos) + mListView.getHeaderViewsCount());
}
processOverlayView(touchPos);
}
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
if (mOverlayView != null) mOverlayView.setVisibility(GONE);
if (mRightOverlayView != null) mRightOverlayView.setVisibility(GONE);
break;
}
return true;
}
private void processRightOverlayView(float y) {
if (mRightOverlayView != null) {
if (y - dp80 > 0) {
mRightOverlayView.setY(y - dp80);
} else {
mRightOverlayView.setY(0);
}
}
}
private void processOverlayView(final int touchPos) {
if (mRightOverlayView != null) {
if (mRightOverlayView.getVisibility() != VISIBLE) {
mRightOverlayView.setVisibility(VISIBLE);
}
mRightOverlayView.setText(mIndex.get(touchPos));
}
if (mOverlayView != null) {
if (mOverlayView.getVisibility() != VISIBLE) {
mOverlayView.setVisibility(VISIBLE);
}
if (mIndex.size() <= touchPos) return;
mOverlayView.setText(mIndex.get(touchPos));
}
if (mOnIndexSelectedListener != null) {
mListView.post(new Runnable() {
@Override
public void run() {
int position = mListView.getFirstVisiblePosition();
String realIndexTitle = mAdapter.getItemTitle(position);
mOnIndexSelectedListener.onSelection(touchPos, realIndexTitle);
}
});
}
}
private String getIndexTitle(int position) {
if (mAdapter == null) return "";
int size = mAdapter.getHeaderSize();
if (position >= size) {
return mIndex.get(position);
} else {
SparseArray<String> map = mAdapter.getTitleMap();
return map.get(map.keyAt(position));
}
}
void setListView(ListView indexListView) {
mIndex.clear();
mListView = indexListView;
ListAdapter listAdapter = mListView.getAdapter();
if (listAdapter instanceof IndexableAdapter) {
mAdapter = (IndexableAdapter) mListView.getAdapter();
} else if (listAdapter instanceof HeaderViewListAdapter) {
ListAdapter adapter = ((HeaderViewListAdapter) listAdapter).getWrappedAdapter();
if (adapter instanceof IndexableAdapter) {
mAdapter = (IndexableAdapter) adapter;
} else {
throw new ClassCastException("Your Adapter must extends IndexableAdapter!");
}
} else {
throw new ClassCastException("Your Adapter must extends IndexableAdapter!");
}
mItems = mAdapter.getSourceItems();
SparseArray<String> indexMap = mAdapter.getTitleMap();
List<String> headerIndexs = mAdapter.getHeaderIndexs();
if (headerIndexs != null && headerIndexs.size() > 0) {
for (String header : headerIndexs) {
mIndex.add(header);
}
}
for (int i = headerIndexs.size(); i < indexMap.size(); i++) {
mIndex.add(indexMap.get(indexMap.keyAt(i)));
}
}
private static class SearchHanlder extends Handler {
private final WeakReference<IndexBar> mIndexBar;
public SearchHanlder(Looper looper, IndexBar indexBar) {
super(looper);
mIndexBar = new WeakReference<>(indexBar);
}
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
IndexBar indexBar = mIndexBar.get();
if (indexBar.mItems == null || indexBar.mAdapter == null) return;
if (indexBar.mOnSearchResultListener != null) {
indexBar.mOnSearchResultListener.onStart();
}
String currentKey = (String) msg.obj;
indexBar.mNeedShutdown = false;
if (indexBar.mFilterList == null) {
indexBar.mFilterList = new ArrayList<>();
}
indexBar.mFilterList.clear();
if (TextUtils.isEmpty(currentKey.trim())) {
indexBar.runOnUIThread(true);
} else {
for (IndexEntity tmp : indexBar.mItems) {
if (indexBar.mNeedShutdown) {
return;
}
String name = tmp.getName();
if (name.contains(currentKey) || tmp.getSpell().startsWith(currentKey)) {
indexBar.mFilterList.add(tmp);
}
}
HashSet<IndexEntity> hashSet = new HashSet(indexBar.mFilterList);
indexBar.mFilterList.clear();
indexBar.mFilterList.addAll(hashSet);
indexBar.runOnUIThread(false);
}
}
}
void searchTextChange(String key) {
mNeedShutdown = true;
if (mSearchHandlerThread == null) {
mSearchHandlerThread = new HandlerThread("Search_Thread");
mSearchHandlerThread.start();
mSearchHandler = new SearchHanlder(mSearchHandlerThread.getLooper(), this);
}
Message msg = Message.obtain();
msg.obj = key;
msg.what = MSG_SEARCH;
mSearchHandler.sendMessage(msg);
}
@Override
protected void onDetachedFromWindow() {
if (mSearchHandlerThread != null) {
mSearchHandlerThread.quit();
}
super.onDetachedFromWindow();
}
void runOnUIThread(final boolean show) {
if (getContext() instanceof Activity) {
((Activity) getContext()).runOnUiThread(new Runnable() {
@Override
public void run() {
if (mNeedShutdown) {
return;
}
if (show) {
setVisibility(VISIBLE);
mAdapter.setFilterDatas(null);
} else {
setVisibility(GONE);
mAdapter.setFilterDatas(mFilterList);
}
if (mOnSearchResultListener != null) {
mOnSearchResultListener.onResult(!show, mFilterList.size());
}
}
});
}
}
void setOnSearchResultListener(OnSearchResultListener listener) {
mOnSearchResultListener = listener;
}
interface OnSearchResultListener {
void onStart();
void onResult(boolean isSearch, int dataSize);
}
void setOverlayView(TextView overlay) {
mOverlayView = overlay;
}
void showTouchOverlayView(TextView overlay) {
mRightOverlayView = overlay;
}
void onListViewScrollStateChanged(int scrollState) {
if (scrollState == 0) mScrollState = -1;
else mScrollState = scrollState;
}
void onListViewScroll(int firstVisibleItem) {
if (mAdapter == null) return;
if (getVisibility() == GONE) return;
if (firstVisibleItem == 0) {
if (mCurrentScrollPos != firstVisibleItem) {
initInvalidate(firstVisibleItem, firstVisibleItem);
}
return;
}
if (mCurrentScrollPos != firstVisibleItem) {
String first;
if (mListView != null) {
if (firstVisibleItem <= mListView.getHeaderViewsCount()) {
first = mAdapter.getItemFirstSpell(0);
} else {
first = mAdapter.getItemFirstSpell(firstVisibleItem - mListView.getHeaderViewsCount());
}
} else {
first = mAdapter.getItemFirstSpell(firstVisibleItem);
}
for (int i = 0; i < mIndex.size(); i++) {
if (first.equals(mIndex.get(i))) {
initInvalidate(firstVisibleItem, i);
return;
}
}
}
}
private void initInvalidate(int firstVisibleItem, int i) {
mCurrentScrollPos = firstVisibleItem;
if (mScrollState != -1) {
mSelectionPos = i;
String indexTitle = getIndexTitle(i);
mOnIndexSelectedListener.onSelection(mSelectionPos, indexTitle);
}
invalidate();
}
public void setOnIndexSelectedListener(OnIndexTitleSelectedListener listener) {
this.mOnIndexSelectedListener = listener;
}
public static int dp2px(Context context, float spValue) {
final float fontScale = context.getResources().getDisplayMetrics().scaledDensity;
return (int) (spValue * fontScale + 0.5f);
}
interface OnIndexTitleSelectedListener {
void onSelection(int position, String indexTitle);
}
}