package com.github.tifezh.kchartlib.chart.rate; import android.animation.ValueAnimator; import android.content.Context; import android.content.res.Configuration; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.support.annotation.ColorRes; import android.support.annotation.DimenRes; import android.support.v4.content.ContextCompat; import android.support.v4.view.GestureDetectorCompat; import android.util.AttributeSet; import android.util.Log; import android.view.MotionEvent; import android.view.ScaleGestureDetector; import com.github.tifezh.kchartlib.R; import com.github.tifezh.kchartlib.utils.DensityUtil; import java.util.ArrayList; import java.util.List; import static android.view.View.MeasureSpec.AT_MOST; /** * Description k线图 * Author puyantao * Email [email protected] * Date 2018-12-4 10:43 */ public abstract class BaseRateView extends ScrollAndScaleView { protected float mTranslateX = Float.MIN_VALUE; protected float mMainScaleY = 1; protected float mDataLen = 0; //数据的长度 protected float downX; protected float downY; protected float mMainMaxValue; //最大值 protected float mMainMinValue;//最小值 protected float mPointWidth = 18; //点的宽度 protected float mOverScrollRange = 0; //设置超出右方后可滑动的范围 protected int mWidth; //试图宽 protected int mHeight;//试图高 protected int mMaxPointSize = 240; //最多点的个数 protected int mPointSize = 60; //默认点的个数 protected int mMinPointSize = 20;//最少点的个数 //点的索引 protected int mStartIndex = 0; protected int mStopIndex = 0; protected int mGridRows = 8; protected int mGridColumns = 6; protected int mSelectedIndex; protected int mMainHeight;//主视图高 protected int mMainWidth;//主视图高 protected int mItemCount;//当前点的个数 protected List<IRate> mPoints; protected Paint mGridPaint = new Paint(Paint.ANTI_ALIAS_FLAG); //网格线画笔 protected Paint mBackgroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG); //背景 protected CallOnClick mCallOnClick;//添加视图点击事件 protected ValueAnimator mAnimator; protected long mAnimationDuration = 500; protected long mClickTime = 0; //点击时间 protected OnSelectedChangedListener mOnSelectedChangedListener = null; protected int mBackgroundColor; protected Bitmap mBitmapLogo = BitmapFactory.decodeResource(getContext().getResources(), R.drawable.logo); protected int mLeftPadding = DensityUtil.dp2px(25); //左padding protected int mRightPadding = DensityUtil.dp2px(25);//右padding protected int mTopPadding = DensityUtil.dp2px(5);//距顶部距离; protected int mBottomPadding = DensityUtil.dp2px(20);//距底部距离 public BaseRateView(Context context) { super(context); initView(); } public BaseRateView(Context context, AttributeSet attrs) { super(context, attrs); initView(); } public BaseRateView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); initView(); } public void setOnViewClickListener(CallOnClick callOnClick) { this.mCallOnClick = callOnClick; } private void initView() { setWillNotDraw(false); mPoints = new ArrayList<>(); mBackgroundColor = Color.parseColor("#2A2D4F"); mBackgroundPaint.setColor(mBackgroundColor); mGridPaint.setColor(Color.parseColor("#15FFFFFF")); //网格线颜色 mGridPaint.setStrokeWidth(dp2px(1)); mDetector = new GestureDetectorCompat(getContext(), this); mScaleDetector = new ScaleGestureDetector(getContext(), this); mAnimator = ValueAnimator.ofFloat(0f, 1f); mAnimator.setDuration(mAnimationDuration); mAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { invalidate(); } }); } //点击, 处理长安时间 @Override public boolean onTouchEvent(MotionEvent event) { switch (event.getAction() & MotionEvent.ACTION_MASK) { case MotionEvent.ACTION_DOWN: mClickTime = System.currentTimeMillis(); break; case MotionEvent.ACTION_MOVE: //一个点的时候滑动 if (event.getPointerCount() == 1) { //长按之后移动 if (isLongPress || !isClosePress) { calculateSelectedX(event.getX()); invalidate(); } } break; case MotionEvent.ACTION_UP: if (!isClosePress) { isLongPress = false; } invalidate(); break; case MotionEvent.ACTION_CANCEL: if (!isClosePress) { isLongPress = false; } invalidate(); break; } this.mDetector.onTouchEvent(event); this.mScaleDetector.onTouchEvent(event); return true; } //抬起, 手指离开触摸屏时触发(长按、滚动、滑动时,不会触发这个手势) @Override public boolean onSingleTapUp(MotionEvent e) { switch (e.getAction()) { case MotionEvent.ACTION_DOWN: break; case MotionEvent.ACTION_UP: //双指点击时不会触发 if (isClosePress) { if (System.currentTimeMillis() - mClickTime < 500) { downX = e.getX(); downY = e.getY(); if (downX > 0 && downX < mWidth) { if (mCallOnClick != null) { if (downY <= mMainHeight) { mCallOnClick.onMainViewClick(); } } } } } else { isClosePress = true; } invalidate(); break; case MotionEvent.ACTION_MOVE: break; } return false; } @Override public void onConfigurationChanged(Configuration newConfig) {//横竖屏切换 super.onConfigurationChanged(newConfig); mMainHeight = mHeight - mTopPadding - mBottomPadding; invalidate(); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec); int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec); int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec); int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec); setMeasuredDimension(widthSpecSize, heightSpecSize); } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); this.mWidth = w; this.mHeight = h; mMainHeight = h - mTopPadding - mBottomPadding; mMainWidth = w - mLeftPadding - mRightPadding; notifyChanged(); setScaleValue(); //计算缩放率 setTranslateXFromScrollX(mScrollX); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); canvas.drawColor(mBackgroundColor); //绘制背景 drawMainViewLogo(canvas); //绘制logo drawGird(canvas); //绘制网格线 } //添加主视图水印 public void drawMainViewLogo(Canvas canvas) { if (mBitmapLogo != null) { int mLeft = getWidth() / 2 - mBitmapLogo.getWidth() / 2; int mTop = (mTopPadding + mMainHeight) / 2 - mBitmapLogo.getHeight() / 2; canvas.drawBitmap(mBitmapLogo, mLeft, mTop, null); } } //画表格 private void drawGird(Canvas canvas) { //横向的grid float rowSpace = mMainHeight / (mGridRows - 1); for (int i = 0; i < mGridRows; i++) { canvas.drawLine(0, rowSpace * i + mTopPadding, mWidth, rowSpace * i + mTopPadding, mGridPaint); } //纵向的grid float columnSpace = mMainWidth / (mGridColumns - 1); for (int i = 0; i < mGridColumns; i++) { canvas.drawLine(columnSpace * i + mLeftPadding, mTopPadding, columnSpace * i + mRightPadding, mMainHeight, mGridPaint); } } private void calculateSelectedX(float x) { mSelectedIndex = indexOfTranslateX(xToTranslateX(x)); Log.e("move mSelectedIndex:", "" + mSelectedIndex + "/mStartIndex:" + mStartIndex); if (mSelectedIndex < mStartIndex) { mSelectedIndex = mStartIndex; } if (mSelectedIndex > mStopIndex) { mSelectedIndex = mStopIndex; } } @Override public void onLongPress(MotionEvent e) { super.onLongPress(e); int lastIndex = mSelectedIndex; calculateSelectedX(e.getX()); if (lastIndex != mSelectedIndex) { onSelectedChanged(this, getItem(mSelectedIndex), mSelectedIndex); } invalidate(); } @Override protected void onScrollChanged(int l, int t, int oldl, int oldt) { super.onScrollChanged(l, t, oldl, oldt); setTranslateXFromScrollX(mScrollX); Log.e("scroll-change", "" + mScrollX); } @Override protected void onScaleChanged(float scale, float oldScale) { super.onScaleChanged(scale, oldScale); checkAndFixScrollX(); setTranslateXFromScrollX(mScrollX); } @Override public int getMinScrollX() { return (int) -(mOverScrollRange / mScaleX); } @Override public int getMaxScrollX() { return Math.round(getMaxTranslateX() - getMinTranslateX()); } //获取平移的最小值 private float getMinTranslateX() { return -mDataLen + mWidth / mScaleX - mPointWidth / 2; } //获取平移的最大值 private float getMaxTranslateX() { if (!isFullScreen()) { //数据不够时 return getMinTranslateX(); } return mPointWidth / 2; } //数据是否充满屏幕 public boolean isFullScreen() { Log.i("mDataLen : ", mDataLen + "-->1"); Log.i("mDataLen : ", mWidth + "-->mWidth"); Log.i("mDataLen : ", mWidth / mScaleX + "-->mWidth / mScaleX"); Log.i("mDataLen : ", mScaleX + "-->mScaleX"); return mDataLen >= mWidth / mScaleX; } public float getXScale() { return mScaleX; } //计算Y轴位置 public float getMainY(float value) { return (mMainMaxValue - value) * mMainScaleY + mTopPadding; } //重新计算并刷新线条 public void notifyChanged() { if (mItemCount != 0) { mDataLen = (mItemCount - 1) * mPointWidth; checkAndFixScrollX(); setTranslateXFromScrollX(mScrollX); } else { setScrollX(0); } invalidate(); } //设置缩放率(并还原 当初设置) public void setScaleValue() { mScaleXMax = mWidth / (mMinPointSize * mPointWidth); // mScrollX = 0; mScaleX = 1; if (mPoints.size() < mMaxPointSize) { mScaleXMin = mWidth / mDataLen; } else { mScaleXMin = mWidth / (mMaxPointSize * mPointWidth); } } //根据索引获取实体 public IRate getItem(int position) { if (mPoints != null && mItemCount != 0) { return mPoints.get(position); } else { return null; } } //根据索引索取x坐标 public float getX(int position) { return position * mPointWidth; } //scrollX 转换为 TranslateX public void setTranslateXFromScrollX(int scrollX) { mTranslateX = scrollX + getMinTranslateX(); } // view中的x转化为TranslateX public float xToTranslateX(float x) { return -mTranslateX + x / mScaleX; } //translateX转化为view中的x public float translateXtoX(float translateX) { return (translateX + mTranslateX) * mScaleX; } //转化成数组的索引 public int indexOfTranslateX(float translateX) { return indexOfTranslateX(translateX, 0, mItemCount - 1); } /** * 二分查找当前值的index * * @return 返回当前位置 */ public int indexOfTranslateX(float translateX, int start, int end) { if (end == start) { return start; } if (end - start == 1) { float startValue = getX(start); float endValue = getX(end); return Math.abs(translateX - startValue) < Math.abs(translateX - endValue) ? start : end; } int mid = start + (end - start) / 2; float midValue = getX(mid); if (translateX < midValue) { return indexOfTranslateX(translateX, start, mid); } else if (translateX > midValue) { return indexOfTranslateX(translateX, mid, end); } else { return mid; } } //开始动画 public void startAnimation() { if (mAnimator != null) { mAnimator.start(); } } //设置动画时间 public void setAnimationDuration(long duration) { if (mAnimator != null) { mAnimator.setDuration(duration); } } //设置表格行数 public void setGridRows(int gridRows) { if (gridRows < 1) { gridRows = 1; } mGridRows = gridRows; } //设置表格列数 public void setGridColumns(int gridColumns) { if (gridColumns < 1) { gridColumns = 1; } mGridColumns = gridColumns; } //获取图的宽度 public int getChartWidth() { return mWidth; } //是否长按 public boolean isLongPress() { return (isLongPress || !isClosePress); } //获取选择索引 public int getSelectedIndex() { return mSelectedIndex; } //设置选择监听 public void setOnSelectedChangedListener(OnSelectedChangedListener l) { this.mOnSelectedChangedListener = l; } public void onSelectedChanged(BaseRateView view, Object point, int index) { if (this.mOnSelectedChangedListener != null) { mOnSelectedChangedListener.onSelectedChanged(view, point, index); } } //设置超出右方后可滑动的范围 public void setOverScrollRange(float overScrollRange) { if (overScrollRange < 0) { overScrollRange = 0; } mOverScrollRange = overScrollRange; } //设置每个点的宽度 public void setPointWidth(float pointWidth) { mPointWidth = pointWidth; } //监听视图点击区域 public interface CallOnClick { void onMainViewClick(); } //选中点变化时的监听 public interface OnSelectedChangedListener { /** * 当选点中变化时 * * @param view 当前view * @param point 选中的点 * @param index 选中点的索引 */ void onSelectedChanged(BaseRateView view, Object point, int index); } public float getDimension(@DimenRes int resId) { return getResources().getDimension(resId); } public int getColor(@ColorRes int resId) { return ContextCompat.getColor(getContext(), resId); } public int dp2px(float dp) { final float scale = getContext().getResources().getDisplayMetrics().density; return (int) (dp * scale + 0.5f); } public int sp2px(float spValue) { final float fontScale = getContext().getResources().getDisplayMetrics().scaledDensity; return (int) (spValue * fontScale + 0.5f); } //释放内存 public void releaseMemory() { if (mBitmapLogo != null) { if (!mBitmapLogo.isRecycled()) { mBitmapLogo.recycle(); mBitmapLogo = null; } } } }