自定义放大缩小曲线图
前言
作为五月的最后一个周末,理论上我应该写一个关于相机的文章,但是因为太忙了,没时间研究,还是把之前写的自定义View拿来充一下数吧,下个月发相机文章,一定.
效果
支持左右滑动和双指缩放(缩放需要使用真机测试)
代码
package com.lwyy.wheel.view;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import com.lwyy.wheel.R;
import com.lwyy.wheel.model.ActivityDetailPoint;
import com.lwyy.wheel.util.TimeUtil;
import com.lwyy.wheel.util.ToolUtil;
import com.lwyy.wheel.util.UnitUtil;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class BezierCurve extends View {
private static final int NONE = 0;
private static final int DRAG = 1;
private static final int ZOOM = 2;
private float typeRateMax = 0.6f;
private Paint mPaint;
private Paint mLinePaint;
private Paint mTimeTextPaint;
private float prevx, lastcurx, curx, cury, curd, prevd;
private int mode = NONE;
private long min_time, max_time, touch_time;
private long total_time, static_min, static_max;
private List<Point> pointList = new ArrayList<>();
private List<ActivityDetailPoint> dataLists = new ArrayList<>();
private float rate;
private int midx;
private int linex;
private long MAX_TIME, MIN_TIME;
private Path path = new Path();
private Rect layoutRect = new Rect();
private int showPadding = 0;
private Context context;
private float keyBase = 0;
private int lineNumY = 5;
private int showTypeYValueArray = 44;
private float maxYPointValue;
private int horizontalNum = 13;
private int distanceW, distanceH;
private int lineStartX;
private String[] dayStrArray = {"00:00", "02:00", "04:00", "06:00", "08:00", "10:00",
"12:00", "14:00", "16:00", "18:00", "20:00", "22:00", "24:00"};
private int minValue = 0;
private int touchIdArray = R.mipmap.activity_detail_blue;
private int lineIdArray = R.color.colorActivityDetailHeartRate;
private Paint mValueLinePaint;
private Paint mTextRectPaint;
public BezierCurve(Context context) {
this(context, null);
}
public BezierCurve(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public BezierCurve(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
this.context = context;
initPaints();
}
private void initPaints() {
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mLinePaint = new Paint();
mLinePaint.setAntiAlias(true);
mLinePaint.setStrokeWidth(1);
mLinePaint.setColor(getResources().getColor(R.color.colorRecyclerItemDividerBg));
mTimeTextPaint = new Paint();
mTimeTextPaint.setAntiAlias(true);
mTimeTextPaint.setTextSize(UnitUtil.dp2px(context, 7));
mTimeTextPaint.setColor(getResources().getColor(R.color.colorTextCustomDeep));
mPaint.setStyle(Paint.Style.FILL);
mPaint.setStrokeWidth(5);
mPaint.setTextSize(UnitUtil.dp2px(context, 10));
mPaint.setTextAlign(Paint.Align.LEFT);
linex = -1;
mTextRectPaint = new Paint();
mTextRectPaint.setAntiAlias(true);
mTextRectPaint.setTextAlign(Paint.Align.CENTER);
mTextRectPaint.setTextSize(UnitUtil.dp2px(context, 10));
mTextRectPaint.setColor(getResources().getColor(R.color.colorTextSelect));
mValueLinePaint = new Paint();
mValueLinePaint.setAntiAlias(true);
}
public void setData(List<ActivityDetailPoint> datasList, int minTime, int maxTime) {
refreshData(datasList, true);
this.MAX_TIME = maxTime;
this.MIN_TIME = minTime;
init();
}
public void refreshData(List<ActivityDetailPoint> datasList, boolean isAutoRefresh) {
if (null == datasList)
return;
if (this.dataLists.size() > 0)
this.dataLists.clear();
this.dataLists.addAll(datasList);
List<Long> xValueList = new ArrayList<>();
List<Float> yValueList = new ArrayList<>();
for (ActivityDetailPoint point : datasList) {
yValueList.add(point.avg);
xValueList.add(point.timeStamp);
}
this.maxYPointValue = Collections.max(yValueList);
this.minValue = 40;
this.keyBase = 45;
if (!isAutoRefresh) {
convertData2Point();
invalidate();
}
}
private void init() {
max_time = MAX_TIME;
min_time = MIN_TIME;
total_time = max_time - min_time;
static_min = min_time;
static_max = max_time;
convertData2Point();
invalidate();
}
private void convertData2Point() {
if (pointList.size() > 0)
pointList.clear();
float rate1 = returnRate();
float keyValueY = distanceH / (lineNumY + 1f);
if (null != dataLists && dataLists.size() > 0) {
for (ActivityDetailPoint heartRateData : dataLists) {
float x = (rate1 * (heartRateData.timeStamp - min_time)) + lineStartX;
float y = distanceH - heartRateData.avg / 1f / keyBase * keyValueY - keyValueY / 2 + keyValueY * minValue / keyBase;
pointList.add(new Point(x, y));
}
}
}
//控制x坐标之间的间隔
private float returnRate() {
float dex = (distanceW + lineStartX * 9f) / (max_time - min_time);
return dex;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction() & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN:
mode = DRAG;
prevx = event.getX();
invalidate();
break;
case MotionEvent.ACTION_MOVE:
if (mode == DRAG) {
lastcurx = curx;
curx = event.getX();
long temp_min_time = (int) ((float) (total_time / distanceW) * (int) (prevx - curx) / 2) + static_min;
temp_min_time = temp_min_time < MIN_TIME ? MIN_TIME : temp_min_time;
long temp_max_time = temp_min_time + total_time;
if (max_time > (MAX_TIME + total_time * typeRateMax) && curx < lastcurx && curx < prevx && lastcurx > 0) {
return false;
}
long tempMinTime = min_time;
long tempMaxTime = max_time;
min_time = temp_min_time;
max_time = temp_max_time;
if (Math.abs((max_time - min_time)) < 3600) {
min_time = tempMinTime;
max_time = tempMaxTime;
break;
}
} else if (mode == ZOOM) {
curd = spacing(event);
rate = prevd / curd;
rate = rate > 10f ? 10f : rate;
rate = rate < 0.1f ? 0.1f : rate;
if (((static_max - static_min) * rate) < 3600) { // 最大值与最小值之差小于1小时,不再缩放了
break;
}
changeTimeRange();
}
convertData2Point();
invalidate();
break;
case MotionEvent.ACTION_UP:
static_max = max_time;
static_min = min_time;
lastcurx = 0;
linex = getClosestValueIndex(prevx);
cury = event.getY();
break;
case MotionEvent.ACTION_POINTER_UP:
mode = NONE;
break;
case MotionEvent.ACTION_POINTER_DOWN:
linex = -1;
midx = (int) (event.getX(0) + event.getX(1)) / 2;
touch_time = x2Timestamp(midx);
mode = ZOOM;
if (event.getPointerCount() > 1) {
prevd = spacing(event);
}
break;
}
return true;
}
@Override
protected void onDraw(Canvas canvas) {
drawXAxis(canvas);
drawYAxis(canvas);
drawBeizer(canvas);
drawTouch(canvas);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int sizeWidth = MeasureSpec.getSize(widthMeasureSpec);
int sizeHeight = MeasureSpec.getSize(heightMeasureSpec);
int resultWidth = sizeWidth;
int resultHeight = sizeHeight;
// 考虑内边距对尺寸的影响
resultWidth += getPaddingLeft() + getPaddingRight();
resultHeight += getPaddingTop() + getPaddingBottom();
// 考虑父容器对尺寸的影响
distanceW = resultWidth = resolveMeasure(sizeWidth, resultWidth);
distanceH = resultHeight = resolveMeasure(sizeHeight, resultHeight);
setMeasuredDimension(resultWidth, resultHeight);
showPadding = distanceW / (horizontalNum + 2);
lineStartX = (distanceW - showPadding * (horizontalNum - 1)) / 2;
layoutRect = new Rect(0, 0, distanceW, showPadding);
init();
}
/**
* 根据传入的值进行测量
*/
public int resolveMeasure(int measureSpec, int defaultSize) {
int result = defaultSize;
int specSize = MeasureSpec.getSize(measureSpec);
switch (MeasureSpec.getMode(measureSpec)) {
case MeasureSpec.AT_MOST:
case MeasureSpec.EXACTLY:
result = Math.min(specSize, defaultSize);
break;
}
return result;
}
// 画x轴
private void drawXAxis(Canvas canvas) {
float rate1 = returnRate();
float y = layoutRect.bottom - 0.5f * getTextHeight();
mTimeTextPaint.setTextAlign(Paint.Align.CENTER);
int showNum = dayStrArray.length;
long timeStamp;
int dex = 2;
for (int i = 0; i < showNum; i++) {
timeStamp = 60 * 60 * i * dex + MIN_TIME;
if (timeStamp >= min_time) {
float x = (rate1 * (timeStamp - min_time)) + lineStartX;
canvas.drawText(dayStrArray[i], x, y, mTimeTextPaint);
}
}
}
// 画Y轴及线
private void drawYAxis(Canvas canvas) {
mPaint.setStyle(Paint.Style.FILL);
mPaint.setStrokeWidth(5);
mPaint.setColor(getResources().getColor(R.color.colorTextCustomDeep));
float keyValueY = distanceH / (lineNumY + 1), yValue;
String showStr;
for (int i = 0; i < lineNumY; i++) {
yValue = keyValueY * (i + 1) + keyValueY;
showStr = ToolUtil.returnFormatNum(String.valueOf(keyBase * (lineNumY - i - 1) + minValue));
if (i < lineNumY - 1) {
canvas.drawLine(lineStartX - showPadding, yValue, distanceW - lineStartX + showPadding, yValue, mLinePaint);
}
canvas.drawText(showStr, lineStartX - showPadding, yValue + 0.5f * getTextHeight() - keyValueY / 2, mPaint);
}
}
private void drawBeizer(Canvas canvas) {
if (pointList != null && pointList.size() > 0) {
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(8);
mPaint.setColor(getResources().getColor(lineIdArray));
Point startp, endp;
for (int i = 0; i < pointList.size() - 1; i++) {
startp = pointList.get(i);
endp = pointList.get(i + 1);
float wt = (startp.px + endp.px) / 2;
path.reset();
path.moveTo(startp.px, startp.py);
path.cubicTo(wt, startp.py, wt, endp.py, endp.px, endp.py);
canvas.drawPath(path, mPaint);
}
}
}
private void drawTouch(Canvas canvas) {
if (pointList.size() == 1) {
linex = 0;
cury = pointList.get(linex).py - 30;
}
if (pointList.size() > 0 && linex >= 0 && linex < pointList.size()) {
String hAndMArray[] = TimeUtil.timeStampToString(dataLists.get(linex).timeStamp, TimeUtil.SDF_TYPE_HM).split(":");
if (null != hAndMArray && hAndMArray.length > 1 && Math.abs(pointList.get(linex).py - cury) < 40) {//0 点
float x = pointList.get(linex).px - getTextHeight();
float y = pointList.get(linex).py - getTextHeight();
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), touchIdArray);
int height = bitmap.getHeight();
canvas.drawBitmap(bitmap, x, y, mValueLinePaint);
canvas.drawBitmap(BitmapFactory.decodeResource(getResources(), R.mipmap.activity_detail_click_value_bg), x - height, y - 2 * height, mValueLinePaint);
canvas.drawText(ToolUtil.returnFormatNum(String.valueOf(dataLists.get(linex).avg)), x + height / 2, y - height, mTextRectPaint);
}
}
}
// 文字的高度
private float getTextHeight() {
Paint.FontMetrics fm = mTimeTextPaint.getFontMetrics();
return (float) Math.ceil(fm.descent - fm.ascent) - 2;
}
private float spacing(MotionEvent event) {
float x = event.getX(0) - event.getX(1);
float y = event.getY(0) - event.getY(1);
return (float) Math.sqrt(x * x + y * y);
}
private void changeTimeRange() {
min_time = (long) (touch_time - (touch_time - static_min) * rate);
max_time = (long) (touch_time + (static_max - touch_time) * rate);
if (min_time <= MIN_TIME)
min_time = MIN_TIME;
if (max_time >= MAX_TIME)
max_time = MAX_TIME;
total_time = max_time - min_time;
}
private int getClosestValueIndex(float x) {
int res = 0;
if (pointList.size() > 0) {
float dif = distanceW / pointList.size(), tmp;
for (int i = 0; i < pointList.size(); i++) {
tmp = Math.abs((x - pointList.get(i).px));
if (tmp < dif) {
dif = tmp;
res = i;
}
}
}
return res;
}
private long x2Timestamp(float x) {
return (long) ((max_time - min_time) * (x / distanceW) + min_time);
}
private class Point {
float px, py;
Point(float px, float py) {
this.px = px;
this.py = py;
}
@Override
public String toString() {
return "px:" + px + ", py:" + py;
}
}
}
地址
传git失败,有时间再传吧