最近在项目中要仿支付宝做一个运动轨迹的曲线图。话不多说先上图
需求 过去7天有动画、时间轴、阴影、点击时显示步数、横向轴有步数均值
我是通过自定义View实现这些需求的,绘制之前需要掌握自定义View的基本操作方法。
代码如下:
import 你项目的包名
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.LinearGradient;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PathMeasure;
import android.graphics.Point;
import android.graphics.Shader;
import android.graphics.SweepGradient;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.animation.AccelerateDecelerateInterpolator;
import com.zdkj.module_sport.R;
import com.zdkj.module_sport.view.activity.api.e.SpetEvent;
import org.greenrobot.eventbus.EventBus;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
/**
* @author guoqiang
* @date 2019/12/18.
* description:1580321809@qq.com
*/
public class UshareChatView extends View {
private Paint xianPaint;
private Paint pointPaint;
private Paint datelinePaint;
private Paint circlePaint;
private Paint textRulerPaint;
private Paint textPointPaint;
private Path linePath;
private Path tablePath;
private Path mFillPath = new Path();
private int mWidth, mHeight;
private List<DataBean> dataList = new ArrayList<>();
private String[] xline = new String[7];
private Point[] linePoints;
private int stepStart;
private int stepEnd;
private int stepSpace;
private int stepSpaceDefault = 43;
private int stepSpaceDP = stepSpaceDefault;
private int topSpace, bottomSpace;
private int tablePadding;
private int tablePaddingDP = 20;
private int maxValue, minValue;
private int rulerValueDefault = 5000;
private int rulerValue = rulerValueDefault;
private int rulerValuePadding;
private int rulerValuePaddingDP = 8;
private float heightPercent = 0.618f;
private float lineWidthDP = 2f;
private int pointColor = Color.parseColor("#5FB4F8");
private float pointWidthDefault = 2f;
private float pointWidthDP = pointWidthDefault;
private int datelineColor = Color.parseColor("#BBBBBB");
private float datelineWidthDefault = 0.5f;
private float datelineWidthDP = 0.5f;//锚点宽度dp
private int tableColor = Color.parseColor("#BBBBBB");
private float tableWidthDP = 0.5f;
private int rulerTextColor = tableColor;
private float rulerTextSizeSP = 10f;
private int pointTextColor = Color.parseColor("#009688");
private float pointTextSizeSP = 10f;
private boolean isShowTable = true;
private boolean isBezierLine = true;
private boolean isInitialized = false;
private boolean isPlayAnim = true;
private ValueAnimator valueAnimator;
private float currentValue = 0f;
private boolean isAnimating = false;
private int endX;
private int startX;
private int index;
public UshareChatView(Context context) {
this(context, null);
}
public UshareChatView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public UshareChatView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
setupView();
}
private void setupView() {
xianPaint = new Paint();
xianPaint.setStyle(Paint.Style.STROKE);
xianPaint.setAntiAlias(true);
LinearGradient linearGradient = new LinearGradient(100, 100, 500, 500, Color.parseColor("#67C0F8"), Color.parseColor("#4487F6"), Shader.TileMode.CLAMP);
int[] colorSweep = {Color.parseColor("#67C0F8"), Color.parseColor("#4487F6"), Color.parseColor("#67C0F8")};
float[] position = {0f, 0.5f, 1f};
Shader shader = new SweepGradient(getWidth() >> 1, getHeight() >> 1, colorSweep, position);
xianPaint.setShader(shader);
xianPaint.setShader(linearGradient);
xianPaint.setStrokeWidth(dip2px(lineWidthDP));
pointPaint = new Paint();
pointPaint.setAntiAlias(true);
pointPaint.setStyle(Paint.Style.STROKE);
pointPaint.setColor(pointColor);
pointPaint.setStrokeWidth(dip2px(pointWidthDP));
datelinePaint = new Paint();
datelinePaint.setAntiAlias(true);
datelinePaint.setStyle(Paint.Style.STROKE);
datelinePaint.setColor(datelineColor);
datelinePaint.setStrokeWidth(dip2px(datelineWidthDP));
circlePaint = new Paint();
circlePaint.setAntiAlias(true);
circlePaint.setStyle(Paint.Style.STROKE);
circlePaint.setColor(tableColor);
circlePaint.setStrokeWidth(dip2px(tableWidthDP));
textRulerPaint = new Paint();
textRulerPaint.setAntiAlias(true);
textRulerPaint.setStyle(Paint.Style.FILL);
textRulerPaint.setTextAlign(Paint.Align.CENTER);
textRulerPaint.setColor(rulerTextColor);
textRulerPaint.setTextSize(sp2px(rulerTextSizeSP));
textPointPaint = new Paint();
textPointPaint.setAntiAlias(true);
textPointPaint.setStyle(Paint.Style.FILL);
textPointPaint.setTextAlign(Paint.Align.CENTER);
textPointPaint.setColor(pointTextColor);
textPointPaint.setTextSize(sp2px(pointTextSizeSP));
linePath = new Path();
tablePath = new Path();
resetParam();
}
private void initAnim() {
valueAnimator = ValueAnimator.ofFloat(0f, 1f).setDuration(dataList.size() * 150);
valueAnimator.setInterpolator(new AccelerateDecelerateInterpolator());
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
currentValue = (float) animation.getAnimatedValue();
postInvalidate();
}
});
valueAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
super.onAnimationStart(animation);
currentValue = 0f;
isAnimating = true;
}
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
currentValue = 1f;
isAnimating = false;
isPlayAnim = false;
}
});
valueAnimator.setStartDelay(500);
}
private void resetParam() {
linePath.reset();
tablePath.reset();
stepSpace = dip2px(stepSpaceDP);
tablePadding = dip2px(tablePaddingDP);
rulerValuePadding = dip2px(rulerValuePaddingDP);
stepStart = tablePadding * (isShowTable ? 2 : 1);
stepEnd = stepStart + stepSpace * (dataList.size() - 1);
topSpace = bottomSpace = tablePadding;
linePoints = new Point[7];
initAnim();
isInitialized = false;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int width = tablePadding + getTableEnd() + getPaddingLeft() + getPaddingRight();
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int height = MeasureSpec.getSize(heightMeasureSpec);
if (MeasureSpec.EXACTLY == heightMode) {
height = getPaddingTop() + getPaddingBottom() + height;
}
setMeasuredDimension(width, height);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mWidth = w;
mHeight = h;
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawColor(Color.TRANSPARENT);
canvas.translate(0f, mHeight / 2f + (getViewDrawHeight() + topSpace + bottomSpace) / 2f);
if (!isInitialized) {
setupLine();
}
drawCircle(canvas);
drawTable(canvas);
drawLinePoints(canvas);
}
private void drawText(Canvas canvas, Paint textPaint, String text, float x, float y) {
canvas.drawText(text, x, y, textPaint);
}
private void drawRulerYText(Canvas canvas, String text, float x, float y) {
textRulerPaint.setTextAlign(Paint.Align.RIGHT);
Paint.FontMetrics fontMetrics = textRulerPaint.getFontMetrics();
float fontTotalHeight = fontMetrics.bottom - fontMetrics.top;
float offsetY = fontTotalHeight / 2 - fontMetrics.bottom;
float newY = y + offsetY;
float newX = x - rulerValuePadding;
drawText(canvas, textRulerPaint, text, newX, newY);
}
private void drawRulerXText(Canvas canvas, String text, float x, float y) {
textRulerPaint.setTextAlign(Paint.Align.CENTER);
Paint.FontMetrics fontMetrics = textRulerPaint.getFontMetrics();
float fontTotalHeight = fontMetrics.bottom - fontMetrics.top;
float offsetY = fontTotalHeight / 2 - fontMetrics.bottom;
float newY = y + offsetY + rulerValuePadding;
drawText(canvas, textRulerPaint, text, x, newY);
}
private void drawLinePointText(Canvas canvas, String text, float x, float y) {
textPointPaint.setTextAlign(Paint.Align.CENTER);
float newY = y - rulerValuePadding;
drawText(canvas, textPointPaint, text, x, newY);
}
private int getTableStart() {
return isShowTable ? stepStart + tablePadding : stepStart;
}
private int getTableEnd() {
return isShowTable ? stepEnd + tablePadding : stepEnd;
}
private void drawCircle(Canvas canvas) {
canvas.translate(0f, -dip2px(10));
int tableEnd = getTableEnd();
int rulerCount = maxValue / rulerValue;
int rulerMaxCount = maxValue % rulerValue > 0 ? rulerCount + 1 : rulerCount;
int rulerMax = rulerValue * rulerMaxCount + rulerValueDefault;
tablePath.moveTo(stepStart, 0);
tablePath.lineTo(stepStart, 0);
tablePath.lineTo(tableEnd, 0);
int startValue = minValue - (minValue > 0 ? 0 : minValue % rulerValue);
int endValue = (maxValue + rulerValue);
do {
int startHeight = -getValueHeight(startValue);
tablePath.moveTo(stepStart, startHeight);
tablePath.lineTo(tableEnd, startHeight);
drawRulerYText(canvas, String.valueOf(startValue), stepStart, startHeight);
startValue += rulerValue;
} while (startValue < endValue);
canvas.drawPath(tablePath, circlePaint);
drawRulerXValue(canvas);
}
private void drawRulerXValue(Canvas canvas) {
if (linePoints == null) return;
for (int i = 0; i < 7; i++) {
Point point = linePoints[i];
if (point == null) break;
drawRulerXText(canvas, xline[i], linePoints[i].x - dip2px(10), 0);
Log.i("qwe", "zhanguoqiang: " + linePoints[i]);
}
if (linePoints != null && linePoints[0] != null) {
startX = linePoints[0].x- dip2px(10);
endX = linePoints[6].x- dip2px(10);
} else {
Log.e("asd", "drawRulerXValue: linePoints空指针");
}
//此处有崩溃
}
private void drawTable(Canvas canvas) {
if (isPlayAnim) {
Path path = new Path();
PathMeasure measure = new PathMeasure(linePath, false);
measure.getSegment(0, currentValue * measure.getLength(), path, true);
canvas.drawPath(path, xianPaint);
} else {
canvas.drawPath(linePath, xianPaint);
}
mFillPath.reset();
mFillPath.addPath(linePath);
mFillPath.lineTo(endX, getViewDrawHeight() - dip2px(80));
mFillPath.lineTo(startX, getViewDrawHeight() - dip2px(80));
mFillPath.close();
int save = canvas.save();
canvas.clipPath(mFillPath);
Drawable drawable = getAllDrawable();
int left = startX, top = -1000, right = mWidth, bottom = endX;
drawable.setBounds(left, top, right, bottom);
drawable.draw(canvas);
canvas.restoreToCount(save);
}
private Drawable getAllDrawable() {
return getResources().getDrawable(R.drawable.path_fill_blue);
}
private void drawLinePoints(Canvas canvas) {
if (linePoints == null) return;
int pointCount = 7;
float pointWidth = dip2px(pointWidthDP) / 2;
if (isPlayAnim) {
pointCount = Math.round(currentValue * 7);
}
for (int i = 0; i < pointCount; i++) {
Point point = linePoints[i];
startX = linePoints[0].x-dip2px(10);
endX = linePoints[6].x-dip2px(10);
if (point == null) break;
if (i == 6)
canvas.drawCircle(point.x - 20, point.y, pointWidth, pointPaint);
if (index == 0) {
canvas.drawLine(linePoints[0].x - dip2px(10), 0, linePoints[0].x - dip2px(10), point.y, datelinePaint);// 180, -26
} else if (index == 1) {
canvas.drawLine(linePoints[1].x - dip2px(10), 0, linePoints[1].x - dip2px(10), point.y, datelinePaint);// 309, -213
} else if (index == 2) {
canvas.drawLine(linePoints[2].x - dip2px(10), 0, linePoints[2].x - dip2px(10), point.y, datelinePaint);// 438, -138
} else if (index == 3) {
canvas.drawLine(linePoints[3].x - dip2px(10), 0, linePoints[3].x - dip2px(10), point.y, datelinePaint);// 567, -176
} else if (index == 4) {
canvas.drawLine(linePoints[4].x - dip2px(10), 0, linePoints[4].x - dip2px(10), point.y, datelinePaint);// 696, 0
} else if (index == 5) {
canvas.drawLine(linePoints[5].x - dip2px(10), 0, linePoints[5].x - dip2px(10), point.y, datelinePaint);// 825, -176
} else if (index == 6) {
canvas.drawLine(linePoints[6].x - dip2px(10), 0, linePoints[6].x - dip2px(10), point.y, datelinePaint);// 954, -168
}
// drawLinePointText(canvas, String.valueOf(dataList.get(i).getValue()), point.x - 30, point.y);
}
}
private int getValueHeight(int value) {
float valuePercent = Math.abs(value - minValue) * 100f / (Math.abs(maxValue - minValue) * 100f);
Log.i("asd", "getValueHeight: " + (int) (getViewDrawHeight() * valuePercent + bottomSpace + 0.5f));
return (int) (getViewDrawHeight() * valuePercent + 0.5f);
}
private float getViewDrawHeight() {
Log.i("", "getViewDrawHeight: " + getMeasuredHeight() * heightPercent);
return getMeasuredHeight() * heightPercent;
}
private void setupLine() {
if (dataList.isEmpty()) return;
int stepTemp = getTableStart();
Point point = new Point();
point.set(stepTemp, -getValueHeight(dataList.get(0).getValue()));//
linePoints[0] = point;
linePath.moveTo(point.x - dip2px(10), point.y);
for (int i = 1; i < dataList.size(); i++) {
DataBean data = dataList.get(i);
Point next = new Point();
next.set(stepTemp += stepSpace, -getValueHeight(data.getValue()));
if (isBezierLine) {
int cW = point.x + stepSpace / 2;
Point p1 = new Point();
p1.set(cW, point.y);
Point p2 = new Point();
p2.set(cW, next.y);
linePath.cubicTo(p1.x - dip2px(10), p1.y, p2.x - dip2px(10), p2.y, next.x - dip2px(10), next.y);
} else {
linePath.lineTo(next.x - dip2px(10), next.y);
}
point = next;
linePoints[i] = next;
}
isInitialized = true;
}
private int dip2px(float dipValue) {
final float scale = getResources().getDisplayMetrics().density;
return (int) (dipValue * scale + 0.5f);
}
private int sp2px(float spValue) {
final float fontScale = getResources().getDisplayMetrics().scaledDensity;
return (int) (spValue * fontScale + 0.5f);
}
private void refreshLayout() {
resetParam();
requestLayout();
postInvalidate();
}
@Override
public boolean onTouchEvent(MotionEvent event) {
float x = event.getX();
float y = event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_UP:
whichCircle(x, y);
break;
}
return true;
}
private void whichCircle(float x, float y) {
Log.i("qwe", "whichCircle: " + x);
if (dip2px(17) < x && x < dip2px(77)) {
index = 0;
Log.i("zxc", "whichCircle: " + String.valueOf(dataList.get(0).getValue()));
EventBus.getDefault().post(new SpetEvent(202, String.valueOf(dataList.get(0).getValue())));
} else if ( dip2px(77) < x && x < dip2px(125)) {
index = 1;
Log.i("zxc", "whichCircle: " + String.valueOf(dataList.get(1).getValue()));
EventBus.getDefault().post(new SpetEvent(202, String.valueOf(dataList.get(1).getValue())));
} else if ( dip2px(77) < x && x < dip2px(173)) {
index = 2;
Log.i("zxc", "whichCircle: " + String.valueOf(dataList.get(2).getValue()));
EventBus.getDefault().post(new SpetEvent(202, String.valueOf(dataList.get(2).getValue())));
} else if (dip2px(173) < x && x < dip2px(221)) {
index = 3;
Log.i("zxc", "whichCircle: " + String.valueOf(dataList.get(3).getValue()));
EventBus.getDefault().post(new SpetEvent(202, String.valueOf(dataList.get(3).getValue())));
} else if (dip2px(221) < x && x <dip2px(269) ) {
index = 4;
Log.i("zxc", "whichCircle: " + String.valueOf(dataList.get(4).getValue()));
EventBus.getDefault().post(new SpetEvent(202, String.valueOf(dataList.get(4).getValue())));
} else if (dip2px(269) < x && x < dip2px(283)) {
index = 5;
Log.i("zxc", "whichCircle: " + String.valueOf(dataList.get(5).getValue()));
EventBus.getDefault().post(new SpetEvent(202, String.valueOf(dataList.get(5).getValue())));
} else if (x > dip2px(283)) {
index = 6;
Log.i("zxc", "whichCircle: " + String.valueOf(dataList.get(index).getValue()));
EventBus.getDefault().post(new SpetEvent(202, String.valueOf(dataList.get(6).getValue())));
}
refreshLayout();
/* if (x == 180) {
} else if (x == 324) {
} else if (x == 468) {
} else if (x == 612) {
} else if (x == 756) {
} else if (x == 900) {
} else if (x == 1044) {
}*/
}
/**
* 设置数据
*
* @param dataList
*/
public void setData(List<DataBean> dataList) {
if (dataList == null) {
throw new RuntimeException("dataList cannot is null!");
}
if (dataList.isEmpty()) return;
this.dataList.clear();
this.dataList.addAll(dataList);
maxValue = Collections.max(this.dataList, new Comparator<DataBean>() {
@Override
public int compare(DataBean o1, DataBean o2) {
return o1.getValue() - o2.getValue();
}
}).getValue();
minValue = Collections.min(this.dataList, new Comparator<DataBean>() {
@Override
public int compare(DataBean o1, DataBean o2) {
return o1.getValue() - o2.getValue();
}
}).getValue();
refreshLayout();
}
public void setDate(String[] dateList) {
if (dateList == null) {
throw new RuntimeException("dateList cannot is null!");
}
if (dateList.length == 0) return;
this.xline = null;
this.xline = dateList;
refreshLayout();
}
public void playAnim() {
this.isPlayAnim = true;
if (isAnimating) return;
if (valueAnimator != null) {
valueAnimator.start();
}
}
public static class DataBean {
int key;
public DataBean(int key) {
this.key = key;
}
public int getValue() {
return key;
}
}
public void setRulerYSpace(int space) {
if (space <= 0) {
space = rulerValueDefault;
}
this.rulerValue = space;
refreshLayout();
}
}
资源文件
drawable文件夹下
path_fill_blue.xml
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<gradient
android:startColor="@color/blue_transparent_start"
android:endColor="@color/blue_transparent_end"
android:angle="90"
/>
</shape>
path_fill_green.xml
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<gradient
android:startColor="@color/green_transparent_start"
android:endColor="@color/green_transparent_end"
android:angle="0"
/>
</shape>
path_fill_pink.xml
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<gradient
android:angle="0"
android:endColor="@color/pink_transparent_end"
android:startColor="@color/pink_transparent_start" />
</shape>
colors.xml文件下
<color name="blue_transparent_start">#0000</color>
<color name="blue_transparent_end">#505FB4F8</color>
<color name="green_transparent_start">#004fd4d0</color>
<color name="green_transparent_end">#804fd4d0</color>
<color name="pink_transparent_start">#00FFB6C1</color>
<color name="pink_transparent_end">#80FFB6C1</color>
结束。
调用
(写在Activity或者Fragment方法中的)我是写在 onCreate()方法中了
↓↓↓
ArrayList<Integer> listValueNum = new ArrayList<>();
listValueNum.add(1000);
listValueNum.add(6000);
listValueNum.add(4000);
listValueNum.add(5000);
listValueNum.add(300);
listValueNum.add(5000);
listValueNum.add(4800);
List<UshareChatView.Data> datas = new ArrayList<>();
for (int value : listValueNum) {
UshareChatView.Data data = new UshareChatView.Data(value);
datas.add(data);
ushareChatView.setData(datas);
if (Collections.max(listValueNum) < 1200) {
ushareChatView.setRulerYSpace(30);
} else if (Collections.max(listValueNum) > 1200 && Collections.max(listValueNum) < 5000) {
ushareChatView.setRulerYSpace(400);
} else {
ushareChatView.setRulerYSpace(5000);
}
if (ushareChatView != null) {
ushareChatView.playAnim();//曲线动画
}
}
//获取当前日期往前推一个星期
public String getOtherDate(int value) {
DateFormat dateFormat = new SimpleDateFormat("dd");
Calendar calendar = Calendar.getInstance();
calendar.set(Calendar.HOUR_OF_DAY, value);
String otherDate = dateFormat.format(calendar.getTime());
// Log.i("asd", "getOtherDate: " + otherDate);
return otherDate;
}
资源文件
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/white"
android:orientation="vertical">
<UshareChatView
android:id="@+id/ushareChatView"
android:layout_width="328dp"
android:layout_height="115dp" />
</LinearLayout>
//手敲操作 欢迎指正