package com.doumee.keyCabinet.ui.view; /** * Created by lvzhihao on 17-5-13. */ import android.animation.ValueAnimator; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Path; import android.graphics.RectF; import android.os.Build; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.View; import com.doumee.keyCabinet.R; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; /** * 波浪侧边栏 * author: imilk * https://github.com/Solartisan/WaveSideBar.git */ public class WaveSideBarView extends View { private static final String TAG = "WaveSlideBarView"; // 计算波浪贝塞尔曲线的角弧长值 private static final double ANGLE = Math.PI * 45 / 180; private static final double ANGLE_R = Math.PI * 90 / 180; private OnTouchLetterChangeListener listener; // 渲染字母表 private List mLetters=new ArrayList<>(); // 当前选中的位置 private int mChoose = -1; // 字母列表画笔 private Paint mLettersPaint = new Paint(); // 提示字母画笔 private Paint mTextPaint = new Paint(); // 波浪画笔 private Paint mWavePaint = new Paint(); private float mTextSize; private float mLargeTextSize; private int mTextColor; private int mWaveColor; private int mTextColorChoose; private int mTextColorBg; private int mWidth; private int mHeight; private int mItemHeight; private int mPadding; private float scanle=1.6f;//文字缩放 // 波浪路径 private Path mWavePath = new Path(); // 圆形路径 private Path mBallPath = new Path(); // 手指滑动的Y点作为中心点 private int mCenterY; //中心点Y // 贝塞尔曲线的分布半径 private int mRadius; // 圆形半径 private int mBallRadius; // 用于过渡效果计算 ValueAnimator mRatioAnimator; // 用于绘制贝塞尔曲线的比率 private float mRatio; // 选中字体的坐标 private float mPosX, mPosY; // 圆形中心点X private float mBallCentreX; private Map letterPositionMap = new HashMap<>(); public WaveSideBarView(Context context) { this(context, null); } public WaveSideBarView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public WaveSideBarView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); init(context, attrs); } public void setLetterPositionMap(Map letterPositionMap) { this.letterPositionMap = letterPositionMap; } private void init(Context context, AttributeSet attrs) { List strings = Arrays.asList(context.getResources().getStringArray(R.array.waveSideBarLetters)); mLetters.addAll(strings); mTextColor = Color.parseColor("#969696"); mWaveColor = Color.parseColor("#be69be91"); mTextColorBg = Color.parseColor("#ffE74F4E"); mTextColorChoose = context.getResources().getColor(android.R.color.white); mTextSize = context.getResources().getDimensionPixelSize(R.dimen.textSize_sidebar); mLargeTextSize = context.getResources().getDimensionPixelSize(R.dimen.large_textSize_sidebar); mPadding = context.getResources().getDimensionPixelSize(R.dimen.textSize_sidebar_padding); if (attrs != null) { TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.WaveSideBarView); mTextColor = a.getColor(R.styleable.WaveSideBarView_sidebarTextColor, mTextColor); mTextColorChoose = a.getColor(R.styleable.WaveSideBarView_sidebarChooseTextColor, mTextColorChoose); mTextColorBg = a.getColor(R.styleable.WaveSideBarView_sidebarChoseBg, mTextColorBg); mTextSize = a.getDimension(R.styleable.WaveSideBarView_sidebarTextSize, mTextSize); mLargeTextSize = a.getFloat(R.styleable.WaveSideBarView_sidebarLargeTextSize, mLargeTextSize); mWaveColor = a.getColor(R.styleable.WaveSideBarView_sidebarBackgroundColor, mWaveColor); mRadius = a.getInt(R.styleable.WaveSideBarView_sidebarRadius, context.getResources().getDimensionPixelSize(R.dimen.radius_sidebar)); mBallRadius = a.getInt(R.styleable.WaveSideBarView_sidebarBallRadius, context.getResources().getDimensionPixelSize(R.dimen.ball_radius_sidebar)); a.recycle(); } mWavePaint = new Paint(); mWavePaint.setAntiAlias(true); mWavePaint.setStyle(Paint.Style.FILL); mWavePaint.setColor(mWaveColor); mTextPaint.setAntiAlias(true); mTextPaint.setColor(mTextColorChoose); mTextPaint.setStyle(Paint.Style.FILL); mTextPaint.setTextSize(mLargeTextSize); mTextPaint.setTextAlign(Paint.Align.CENTER); mChoose = 0; } /** * * @param datas item已按照字母顺序排好序的数据 */ public void setData(List datas) { if (datas==null||datas.size()<=0){ return; } mLetters.clear(); letterPositionMap.clear(); /*for (int i = 0; i < datas.size(); i++) { String headPinyin = PinYinUtil.getPinyin(onLetterGet.letterGet(datas.get(i))).substring(0, 1); if (i == 0) { mLetters.add(headPinyin); letterPositionMap.put(headPinyin, from + i); } else { if (!headPinyin.equals(mLetters.get(mLetters.size() - 1))) { mLetters.add(headPinyin); letterPositionMap.put(headPinyin, from + i); } } }*/ for (int i = 0; i < datas.size(); i++) { String headPinyin = datas.get(i); if (i == 0) { mLetters.add(headPinyin); letterPositionMap.put(headPinyin,i); } else { if (!headPinyin.equals(mLetters.get(mLetters.size() - 1))) { mLetters.add(headPinyin); letterPositionMap.put(headPinyin, i); } } } resetItemHeight(); mChoose = 0; invalidate(); } public interface OnLetterGet { String letterGet(T t); } @Override public boolean dispatchTouchEvent(MotionEvent event) { final float y = event.getY(); final float x = event.getX(); int startY=mHeight/2-(mItemHeight*mLetters.size())/2; final int oldChoose = mChoose; int newChoose = (int) ((y-startY) / mItemHeight); if (newChoose<0){ newChoose=0; } if (newChoose>mLetters.size()-1){ newChoose=mLetters.size()-1; } switch (event.getAction()) { case MotionEvent.ACTION_DOWN: if (x < mWidth - 2 * mRadius) { return false; } startAnimator(mRatio, 1.0f); mCenterY = (int) y; if (oldChoose != newChoose) { if (newChoose >= 0 && newChoose < mLetters.size()) { mChoose = newChoose; if (listener != null) { listener.onLetterChange(mLetters.get(newChoose)); } } } invalidate(); break; case MotionEvent.ACTION_MOVE: mCenterY = (int) y; if (oldChoose != newChoose) { if (newChoose >= 0 && newChoose < mLetters.size()) { mChoose = newChoose; if (listener != null) { listener.onLetterChange(mLetters.get(newChoose)); } } } invalidate(); break; case MotionEvent.ACTION_CANCEL: case MotionEvent.ACTION_UP: startAnimator(mRatio, 0f); break; default: break; } return true; } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); mHeight = MeasureSpec.getSize(heightMeasureSpec); mWidth = getMeasuredWidth(); //默认字母所占高度为字体大小的2倍,当字母太多时,高度平均分配 resetItemHeight(); } private void resetItemHeight() { if (mLetters.size()<=0){ return; } mItemHeight = (mHeight - (int)(scanle*mTextSize) )/ mLetters.size(); if (mItemHeight>=(scanle*mTextSize)){ mItemHeight=(int)(scanle*mTextSize); } mPosX = mWidth - 1.6f * mTextSize; } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); //绘制字母列表 drawLetters(canvas); //绘制波浪 //drawWavePath(canvas); //绘制圆 //drawBallPath(canvas); //绘制选中的字体 drawChooseText(canvas); } private void drawLetters(Canvas canvas) { RectF rectF = new RectF(); rectF.left = mPosX - mTextSize; rectF.right = mPosX + mTextSize; rectF.top = 0; rectF.bottom = mHeight; // mLettersPaint.reset(); // mLettersPaint.setStyle(Paint.Style.FILL); // mLettersPaint.setColor(Color.parseColor("#F9F9F9")); // mLettersPaint.setAntiAlias(true); // canvas.drawRoundRect(rectF, mTextSize, mTextSize, mLettersPaint); // // mLettersPaint.reset(); // mLettersPaint.setStyle(Paint.Style.STROKE); // mLettersPaint.setColor(mTextColor); // mLettersPaint.setAntiAlias(true); // canvas.drawRoundRect(rectF, mTextSize, mTextSize, mLettersPaint); int startY=mHeight/2-(mItemHeight*mLetters.size())/2; for (int i = 0; i < mLetters.size(); i++) { mLettersPaint.reset(); mLettersPaint.setColor(mTextColor); mLettersPaint.setAntiAlias(true); mLettersPaint.setTextSize(mTextSize); mLettersPaint.setTextAlign(Paint.Align.CENTER); Paint.FontMetrics fontMetrics = mLettersPaint.getFontMetrics(); float posY = startY+mItemHeight * i +mItemHeight/2+ ((fontMetrics.bottom - fontMetrics.top) / 2 - fontMetrics.bottom); if (i == mChoose) { mPosY = posY; Paint paint = new Paint(); paint.setColor(mTextColorBg); int r= (int) ((rectF.right-rectF.left)/2f); canvas.drawCircle(rectF.left+r, (float) (startY+mItemHeight*i+scanle*mTextSize*0.5f+1),r-10,paint); //canvas.drawRect(rectF.left ,startY+mItemHeight*i+1,rectF.right,startY+mItemHeight*(i+1)-1,paint); } else { canvas.drawText(mLetters.get(i), mPosX, posY, mLettersPaint); } } } private void drawChooseText(Canvas canvas) { if (mChoose != -1) { // 绘制右侧选中字符 mLettersPaint.reset(); mLettersPaint.setColor(mTextColorChoose); mLettersPaint.setTextSize(mTextSize); mLettersPaint.setTextAlign(Paint.Align.CENTER); canvas.drawText(mLetters.get(mChoose), mPosX, mPosY, mLettersPaint); // 绘制提示字符 if (mRatio >= 0.9f) { String target = mLetters.get(mChoose); Paint.FontMetrics fontMetrics = mTextPaint.getFontMetrics(); float baseline = Math.abs(-fontMetrics.bottom - fontMetrics.top); float x = mBallCentreX; float y = mCenterY + baseline / 2; canvas.drawText(target, x, y, mTextPaint); } } } /** * 绘制波浪 * * @param canvas */ private void drawWavePath(Canvas canvas) { mWavePath.reset(); // 移动到起始点 mWavePath.moveTo(mWidth, mCenterY - 3 * mRadius); //计算上部控制点的Y轴位置 int controlTopY = mCenterY - 2 * mRadius; //计算上部结束点的坐标 int endTopX = (int) (mWidth - mRadius * Math.cos(ANGLE) * mRatio); int endTopY = (int) (controlTopY + mRadius * Math.sin(ANGLE)); mWavePath.quadTo(mWidth, controlTopY, endTopX, endTopY); //计算中心控制点的坐标 int controlCenterX = (int) (mWidth - 1.8f * mRadius * Math.sin(ANGLE_R) * mRatio); int controlCenterY = mCenterY; //计算下部结束点的坐标 int controlBottomY = mCenterY + 2 * mRadius; int endBottomX = endTopX; int endBottomY = (int) (controlBottomY - mRadius * Math.cos(ANGLE)); mWavePath.quadTo(controlCenterX, controlCenterY, endBottomX, endBottomY); mWavePath.quadTo(mWidth, controlBottomY, mWidth, controlBottomY + mRadius); mWavePath.close(); canvas.drawPath(mWavePath, mWavePaint); } private void drawBallPath(Canvas canvas) { //x轴的移动路径 mBallCentreX = (mWidth + mBallRadius) - (2.0f * mRadius + 2.0f * mBallRadius) * mRatio; mBallPath.reset(); mBallPath.addCircle(mBallCentreX, mCenterY, mBallRadius, Path.Direction.CW); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { mBallPath.op(mWavePath, Path.Op.DIFFERENCE); } mBallPath.close(); canvas.drawPath(mBallPath, mWavePaint); } private void startAnimator(float... value) { /*if (mRatioAnimator == null) { mRatioAnimator = new ValueAnimator(); } mRatioAnimator.cancel(); mRatioAnimator.setFloatValues(value); mRatioAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator value) { mRatio = (float) value.getAnimatedValue(); invalidate(); } }); mRatioAnimator.start();*/ } public void setOnTouchLetterChangeListener(OnTouchLetterChangeListener listener) { this.listener = listener; } public List getLetters() { return mLetters; } public void setLetters(List letters) { this.mLetters = letters; invalidate(); } public interface OnTouchLetterChangeListener { void onLetterChange(String letter); } }