package com.esaysidebar.lib;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.TypedValue;
import android.view.MotionEvent;
import android.view.View;
import static android.R.attr.width;
/**
* @TODO<WaveSideBar>
* @author 小嵩
* @date 2017-3-9 10:37:24
*/
public class EasySideBar extends View {
private final static int DEFAULT_TEXT_SIZE = 14; // sp
private final static int DEFAULT_MAX_OFFSET = 80; //dp
private final static String[] DEFAULT_INDEX_ITEMS = {"A", "B", "C", "D", "E", "F", "G", "H", "I",
"J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "#"};
private String[] mIndexItems;
/**
* the index in {@link #mIndexItems} of the current selected index item,
* it's reset to -1 when the finger up
*/
private int mCurrentIndex = -1;
/**
* Y coordinate of the point where finger is touching,
* the baseline is top of {@link #mStartTouchingArea}
* it's reset to -1 when the finger up
*/
private float mCurrentY = -1;
private Paint mPaint;
private int mTextColor;
private float mTextSize;
private int MaxHeight;
private int MaxWidth;
/**
* the Height of each index item
*/
private float mIndexItemHeight;
/**
* offset of the current selected index item
*/
private float mMaxOffset;
/**
* {@link #mStartTouching} will be set to true when {@link MotionEvent#ACTION_DOWN}
* happens in this area, and the side bar should start working.
*/
private RectF mStartTouchingArea = new RectF();
/**
* Height and width of {@link #mStartTouchingArea}
*/
private float mBarHeight;
private float mBarWidth;
/**
* Flag that the finger is starting touching.
* If true, it means the {@link MotionEvent#ACTION_DOWN} happened but
* {@link MotionEvent#ACTION_UP} not yet.
*/
private boolean mStartTouching = false;
/**
* if true, the {@link OnSelectIndexItemListener#onSelectIndexItem(int,String)}
* will not be called until the finger up.
* if false, it will be called when the finger down, up and move.
*/
private boolean mLazyRespond = false;
/**
* the position of the side bar, default is {@link #POSITION_RIGHT}.
* You can set it to {@link #POSITION_LEFT} for people who use phone with left hand.
*/
private int mSideBarPosition;
public static final int POSITION_RIGHT = 0;
public static final int POSITION_LEFT = 1;
/**
* the alignment of items, default is {@link #TEXT_ALIGN_CENTER}.
*/
private int mTextAlignment;
public static final int TEXT_ALIGN_CENTER = 0;
public static final int TEXT_ALIGN_LEFT = 1;
public static final int TEXT_ALIGN_RIGHT = 2;
/**
* observe the current selected index item
*/
private OnSelectIndexItemListener onSelectIndexItemListener;
/**
* the baseline of the first index item text to draw
*/
private float mFirstItemBaseLineY;
/**
* for {@link #dp2px(int)} and {@link #sp2px(int)}
*/
private DisplayMetrics mDisplayMetrics;
public EasySideBar(Context context) {
this(context, null);
}
public EasySideBar(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public EasySideBar(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mDisplayMetrics = context.getResources().getDisplayMetrics();
mTextColor = Color.GRAY;
mMaxOffset = dp2px(DEFAULT_MAX_OFFSET);
mSideBarPosition = POSITION_RIGHT;
mTextAlignment = TEXT_ALIGN_CENTER;
/*TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.WaveSideBar);
mLazyRespond = typedArray.getBoolean(R.styleable.WaveSideBar_sidebar_lazy_respond, false);
mTextColor = typedArray.getColor(R.styleable.WaveSideBar_sidebar_text_color, Color.GRAY);
mMaxOffset = typedArray.getDimension(R.styleable.WaveSideBar_sidebar_max_offset, dp2px(DEFAULT_MAX_OFFSET));
mSideBarPosition = typedArray.getInt(R.styleable.WaveSideBar_sidebar_position, POSITION_RIGHT);
mTextAlignment = typedArray.getInt(R.styleable.WaveSideBar_sidebar_text_alignment, TEXT_ALIGN_CENTER);
typedArray.recycle();*/
mTextSize = sp2px(DEFAULT_TEXT_SIZE);
mIndexItems = DEFAULT_INDEX_ITEMS;
initPaint();
}
private void initPaint() {
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setColor(mTextColor);
mPaint.setTextSize(mTextSize);
switch (mTextAlignment) {
case TEXT_ALIGN_CENTER: mPaint.setTextAlign(Paint.Align.CENTER); break;
case TEXT_ALIGN_LEFT: mPaint.setTextAlign(Paint.Align.LEFT); break;
case TEXT_ALIGN_RIGHT: mPaint.setTextAlign(Paint.Align.RIGHT); break;
}
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
MaxHeight = MeasureSpec.getSize(heightMeasureSpec);
MaxWidth = MeasureSpec.getSize(widthMeasureSpec);
Paint.FontMetrics fontMetrics = mPaint.getFontMetrics();
mIndexItemHeight = fontMetrics.bottom - fontMetrics.top;
mBarHeight = mIndexItems.length * mIndexItemHeight;
while (mBarHeight >= MaxHeight){
mTextSize--;
mPaint.setTextSize(mTextSize);
fontMetrics = mPaint.getFontMetrics();
mIndexItemHeight = fontMetrics.bottom - fontMetrics.top;
mBarHeight = mIndexItems.length * mIndexItemHeight;
}
// calculate the width of the longest text as the width of side bar
for (String indexItem : mIndexItems) {
mBarWidth = Math.max(mBarWidth, mPaint.measureText(indexItem));
}
float areaLeft = (mSideBarPosition == POSITION_LEFT) ? 0 : (MaxWidth - mBarWidth - getPaddingRight());
float areaRight = (mSideBarPosition == POSITION_LEFT) ? (getPaddingLeft() + areaLeft + mBarWidth) : width;
float areaTop = MaxHeight /2 - mBarHeight/2;
float areaBottom = areaTop + mBarHeight;
mStartTouchingArea.set(
areaLeft,
areaTop,
areaRight,
areaBottom);
// the baseline Y of the first item' text to draw
mFirstItemBaseLineY = (MaxHeight /2 - mIndexItems.length*mIndexItemHeight/2)
+ (mIndexItemHeight/2 - (fontMetrics.descent-fontMetrics.ascent)/2)
- fontMetrics.ascent;
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// draw each item
for (int i = 0; i < mIndexItems.length; i++) {
float baseLineY = mFirstItemBaseLineY + mIndexItemHeight*i;
// calculate the scale factor of the item to draw
float scale = getItemScale(i);
int alphaScale = (i == mCurrentIndex) ? (255) : (int) (255 * (1-scale));
mPaint.setAlpha(alphaScale);
mPaint.setTextSize(mTextSize + mTextSize*scale);
float baseLineX = 0f;
if (mSideBarPosition == POSITION_LEFT) {
switch (mTextAlignment) {
case TEXT_ALIGN_CENTER:
baseLineX = getPaddingLeft() + mBarWidth/2 + mMaxOffset*scale;
break;
case TEXT_ALIGN_LEFT:
baseLineX = getPaddingLeft() + mMaxOffset*scale;
break;
case TEXT_ALIGN_RIGHT:
baseLineX = getPaddingLeft() + mBarWidth + mMaxOffset*scale;
break;
}
} else {
switch (mTextAlignment) {
case TEXT_ALIGN_CENTER:
baseLineX = getWidth() - getPaddingRight() - mBarWidth/2 - mMaxOffset*scale;
break;
case TEXT_ALIGN_RIGHT:
baseLineX = getWidth() - getPaddingRight() - mMaxOffset*scale;
break;
case TEXT_ALIGN_LEFT:
baseLineX = getWidth() - getPaddingRight() - mBarWidth - mMaxOffset*scale;
break;
}
}
// draw
canvas.drawText(
mIndexItems[i], //item text to draw
baseLineX, //baseLine X
baseLineY, // baseLine Y
mPaint);
}
// reset paint
mPaint.setAlpha(255);
mPaint.setTextSize(mTextSize);
}
/**
* calculate the scale factor of the item to draw
*
* @param index the index of the item in array {@link #mIndexItems}
* @return the scale factor of the item to draw
*/
private float getItemScale(int index) {
float scale = 0;
if (mCurrentIndex != -1) {
float distance = Math.abs(mCurrentY - (mIndexItemHeight*index+mIndexItemHeight/2)) / mIndexItemHeight;
scale = 1 - distance*distance/16;
scale = Math.max(scale, 0);
}
return scale;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
if (mIndexItems.length == 0) {
return super.onTouchEvent(event);
}
float eventY = event.getY();
float eventX = event.getX();
mCurrentIndex = getSelectedIndex(eventY);
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
if (mStartTouchingArea.contains(eventX, eventY)) {
mStartTouching = true;
if (!mLazyRespond && onSelectIndexItemListener != null) {
onSelectIndexItemListener.onSelectIndexItem(mCurrentIndex,mIndexItems[mCurrentIndex]);
}
invalidate();
return true;
} else {
mCurrentIndex = -1;
return false;
}
case MotionEvent.ACTION_MOVE:
if (mStartTouching && !mLazyRespond && onSelectIndexItemListener != null) {
onSelectIndexItemListener.onSelectIndexItem(mCurrentIndex,mIndexItems[mCurrentIndex]);
}
invalidate();
return true;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
if (mLazyRespond && onSelectIndexItemListener != null) {
onSelectIndexItemListener.onSelectIndexItem(mCurrentIndex,mIndexItems[mCurrentIndex]);
}
mCurrentIndex = -1;
mStartTouching = false;
invalidate();
return true;
}
return super.onTouchEvent(event);
}
private int getSelectedIndex(float eventY) {
mCurrentY = eventY - (getHeight()/2 - mBarHeight /2);
if (mCurrentY <= 0) {
return 0;
}
int index = (int) (mCurrentY / this.mIndexItemHeight);
if (index >= this.mIndexItems.length) {
index = this.mIndexItems.length - 1;
}
return index;
}
private float dp2px(int dp) {
return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, this.mDisplayMetrics);
}
private float sp2px(int sp) {
return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, sp, this.mDisplayMetrics);
}
//Customize
public void setIndexItems(String[] indexItems) {
this.mIndexItems = indexItems;
requestLayout();
}
public void setTextColor(int color) {
this.mTextColor = color;
mPaint.setColor(color);
invalidate();
}
public void setPosition(int position) {
if (position != POSITION_RIGHT && position != POSITION_LEFT) {
throw new IllegalArgumentException("the position must be POSITION_RIGHT or POSITION_LEFT");
}
mSideBarPosition = position;
requestLayout();
}
public void setMaxOffset(int offset) {
mMaxOffset = dp2px(offset);
invalidate();
}
public void setLazyRespond(boolean lazyRespond) {
mLazyRespond = lazyRespond;
}
public void setTextAlign(int align) {
if (mTextAlignment == align) {
return;
}
switch (align) {
case TEXT_ALIGN_CENTER: mPaint.setTextAlign(Paint.Align.CENTER); break;
case TEXT_ALIGN_LEFT: mPaint.setTextAlign(Paint.Align.LEFT); break;
case TEXT_ALIGN_RIGHT: mPaint.setTextAlign(Paint.Align.RIGHT); break;
default:
throw new IllegalArgumentException(
"the alignment must be TEXT_ALIGN_CENTER, TEXT_ALIGN_LEFT or TEXT_ALIGN_RIGHT");
}
mTextAlignment = align;
invalidate();
}
public void setOnSelectIndexItemListener(OnSelectIndexItemListener onSelectIndexItemListener) {
this.onSelectIndexItemListener = onSelectIndexItemListener;
}
public interface OnSelectIndexItemListener {
void onSelectIndexItem(int index, String indexValue);
}
}