我们自定义一个View
实现起来还是很简单的就是使用canvas的绘制path的api。最主要的就是绘制的坐标的计算。滑动方面以前使onTouchEvent里面监听手势重新绘制,后来改成使用Scroller,动画方面使用属性动画。
(1)
按照自定义View的步骤,先重写他的几个构造方法。在构造方法中初始化我们需要用到的一些变量,画笔,path路径,间距等。
(2)
在onSizeChanged(在控件大小发生改变时调用。所以这里初始化会被调用一次)方法中初始化控件的宽和高,绘图区域的高度等尺寸值
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
mTotalWidth = getMeasuredWidth();
mTotalHeight = getMeasuredHeight();
setNeedHeight();
leftWhiteRect = new Rect(0, 0, 0, mTotalHeight);
rightWhiteRect = new Rect(mTotalWidth - leftMargin * 2, 0, mTotalWidth, mTotalHeight);
topWhiteRect = new Rect(0, 0, mTotalWidth, topMargin / 2);
bottomWhiteRect = new Rect(0, (int) yStartIndex, mTotalWidth, mTotalHeight);
super.onSizeChanged(w, h, oldw, oldh);
}
/**
* 设置矩形的顶部 底部 右边Y轴的3部分每部分的高度
*/
private void setNeedHeight() {
paintTop = topMargin * 2;
paintBottom = mTotalHeight - topMargin / 2;
maxHeight = paintBottom - paintTop;
yStartIndex = mTotalHeight - topMargin / 2;
;
}
(3)
在onDraw里面进行绘制
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
linePoints.clear();
canvas.drawColor(BG_COLOR);
if (mData == null) return;
//得到每个bar的宽度
getItemsWidth();
//重置线
linePath.reset();
linePath.incReserve(mData.size());
checkTheLeftMoving();
canvas.drawRect(bottomWhiteRect, bgPaint);
canvas.drawRect(topWhiteRect, bgPaint);
//画中间的白线
drawWhiteLine(canvas);
//画线形图
drawLines(canvas);
//画线型图
//画左边和右边的遮罩层
leftWhiteRect.right = (int) xStartIndex;
canvas.drawRect(leftWhiteRect, bgPaint);
canvas.drawRect(rightWhiteRect, bgPaint);
//画线上的点
drawCircles(canvas);
//画左边的Y轴
canvas.drawLine(xStartIndex, yStartIndex, xStartIndex, topMargin / 2, axisPaint);
//左边Y轴的单位
canvas.drawText(leftAxisUnit, xStartIndex, topMargin / 2 - 14, textPaint);
//画右边的Y轴
canvas.drawLine(mTotalWidth - leftMargin * 2, yStartIndex, mTotalWidth - leftMargin * 2, topMargin / 2, axisPaint);
//画左边的Y轴text
drawLeftYAxis(canvas);
//画X轴 下面的和上面
canvas.drawLine(xStartIndex, yStartIndex, mTotalWidth - leftMargin*2, yStartIndex, axisPaint);
canvas.drawLine(xStartIndex, topMargin / 2, mTotalWidth - leftMargin*2, topMargin / 2, axisPaint);
//画X轴的text
drawXAxisText(canvas);
}
(4)
当数据很多的时候 我们希望可以通过拖拽来滑动视图,这就需要我们在绘制的时候先加上一个变量leftMoving,在onTouchEvent中改变leftMoving的值然后invalidate();重新绘制达到拖动的效果
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
lastPointX = event.getRawX();
break;
case MotionEvent.ACTION_MOVE:
float movex = event.getRawX();
movingThisTime = lastPointX - movex;
leftMoving = leftMoving + movingThisTime;
lastPointX = movex;
invalidate();
break;
case MotionEvent.ACTION_UP:
new Thread(new SmoothScrollThread(movingThisTime)).start();
break;
default:
return super.onTouchEvent(event);
}
return true;
}
(5)
当手指停下的时候 动画不立即停止,先滑动一会慢慢的停止。这样体验比较好。 做法就是起一个线程动态改变leftMoving的值刷新界面
/**
* 左右滑动的时候 当手指抬起的时候 使滑动慢慢停止 不会立刻停止
*/
private class SmoothScrollThread implements Runnable {
float lastMoving;
boolean scrolling = true;
private SmoothScrollThread(float lastMoving) {
this.lastMoving = lastMoving;
scrolling = true;
}
@Override
public void run() {
while (scrolling) {
long start = System.currentTimeMillis();
lastMoving = (int) (0.9f * lastMoving);
leftMoving += lastMoving;
checkTheLeftMoving();
postInvalidate();
if (Math.abs(lastMoving) < 5) {
scrolling = false;
}
long end = System.currentTimeMillis();
if (end - start < 20) {
try {
Thread.sleep(20 - (end - start));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
(6)滑动优化
上面的第五步,当手指抬起的时候视图继续滑动一段距离。这种fling效果是通过一个子线程动态减少滑动的值然后更新视图实现的。效果虽然实现了,但是不够流畅,其实安卓已经提供了速度相关的api供我们使用。体验也会更好。
速度相关:VelocityTracker
滑动相关:Scroller
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
lastPointX = event.getX();
scroller.abortAnimation();//如果在滑动终止动画
initOrResetVelocityTracker();//初始化速度跟踪器
velocityTracker.addMovement(event);//将用户的action添加到跟踪器中。
break;
case MotionEvent.ACTION_MOVE:
float movex = event.getX();
movingThisTime = lastPointX - movex;
leftMoving = leftMoving + movingThisTime;
lastPointX = movex;
invalidate();
velocityTracker.addMovement(event);//将用户的action添加到跟踪器中。
break;
case MotionEvent.ACTION_UP:
// new Thread(new SmoothScrollThread(movingThisTime)).start();
velocityTracker.addMovement(event);
velocityTracker.computeCurrentVelocity(1000, maxVelocity);//根据已经到达的点计算当前速度。
int initialVelocity = (int) velocityTracker.getXVelocity();//获得最后的速度
velocityTracker.clear();
//通过scroller让它飞起来
scroller.fling((int) event.getX(), (int) event.getY(), -initialVelocity / 2,
0, Integer.MIN_VALUE, Integer.MAX_VALUE, 0, 0);
invalidate();
lastPointX = event.getX();
break;
case MotionEvent.ACTION_CANCEL:
recycleVelocityTracker();//回收速度跟踪器
break;
default:
return super.onTouchEvent(event);
}
if (mGestureListener != null) {
mGestureListener.onTouchEvent(event);
}
return true;
}
使用Scroller需要重写计算滑动的方法 因为我们的滑动最终还是通过leftMoving 的值来控制的,所以Scroller中计算的也是leftMoving 的值的变化
@Override
public void computeScroll() {
if (scroller.computeScrollOffset()) {
movingThisTime = (scroller.getCurrX() - lastPointX);
leftMoving = leftMoving + movingThisTime;
lastPointX = scroller.getCurrX();
postInvalidate();
}
}
OK这样在滑动的时候就会感觉更流畅了
(7)动画 使用属性动画控制
private float percent = 1f;
private TimeInterpolator pointInterpolator = new DecelerateInterpolator();
public void startAnimation(int duration){
ValueAnimator mAnimator = ValueAnimator.ofFloat(0,1);
mAnimator.setDuration(duration);
mAnimator.setInterpolator(pointInterpolator);
mAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
percent = (float) animation.getAnimatedValue();
invalidate();
}
});
mAnimator.start();
}
上面是一个0-1的属性动画来改变percent 的值,所以percent 就是从0慢慢增长到1的值,我们绘制的时候对于Y坐标的处理就可以用Y坐标的值乘以percent 。这样Y值就是从0到它本来的值慢慢增长完成动画。
代码地址:https://github.com/chsmy/EasyChartWidget