效果:
图像会随着手指左右移动,就这个效果,很简单。
源代码地址:
https://github.com/SunPointed/ShowDataViewDemo
思路:
重点在于对数据的处理,如果一次性把数据画完,实现滑动必然很简单。但是当数据量特别大时,一次性画完展示的却始终是屏幕大小里的数据,内存中保存了一个巨大的画布,显然行不通(我也不是很清楚,但是感觉这样做是不对的,比如有几千的数据,屏幕显示的只是10个)。那么只能每次画一部分,这样内存就不会有问题,但是现在图像随着手指滑动的事件就不那么容易处理了,当滑动超过一定距离后,必然要重新加载数据。
这里假设屏幕中展示10个数据,我们一共画14个数据,那么又分为2种情况:
1.数据量小于等于14,直接全部画出来就行。
2.数据量大于14,需要在滑动中重新加载。
步骤:
1.继承view,重写onDraw,onMeasure,onTouchEvent。在onMeasure中重新计算view的大小,见注释。
public class ShowDataView extends View {
public ShowDataView(Context context) {
this(context, null);
}
public ShowDataView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public ShowDataView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected void onDraw(Canvas canvas) {
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
mHeight = MeasureSpec.getSize(heightMeasureSpec); //控件显示高度
mScreenWidth = MeasureSpec.getSize(widthMeasureSpec);//控件显示宽度
mDataLength = mScreenWidth * 0.55f / DATA_IN_SCREEN_NUMBER; //展示数据的宽度
mSpaceLength = mScreenWidth * 0.45f / DATA_IN_SCREEN_NUMBER; //展示数据间空白的宽度
mSpaceHeight = mHeight / 30; //中部折线与柱状图分界高度
mTextHeight = mHeight / 9; //底部文字显示高度
mAreaHeight = (mHeight - mSpaceHeight - mTextHeight) / 2; //折线图与柱状图高度
mWidth = (mDataLength + mSpaceLength) * LIST_SIZE; //控件实际宽度
mLength = 0; //关键变量,决定何时视图的更新
//关键变量,此处画了14个数据(14个mDataLength + mSpaceLength),始终展示中间10个
mScreenPosition = (mDataLength + mSpaceLength) * 2; setMeasuredDimension((int) mWidth, (int) mHeight);
}
@Override
public void layout(int l, int t, int r, int b) { super.layout(l, t, r, b); }
@Override
public boolean onTouchEvent(MotionEvent event)
{ return true; }
}
2.在ondraw中进行绘图
mDataSize = mMileList.size();
if (mDataSize < LIST_SIZE + 1) {
mLess14 = true;
mSpaceLength = (mWidth - mDataLength * mDataSize) / (mDataSize + 1);
Log.i("LQY", "mSpaceLength1->" + mSpaceLength);
mLength = 0;
mLeftPosition = 0;
} else {
mLess14 = false;
mDataSize = LIST_SIZE;
mMaxLeftPosition = mMileList.size() - mDataSize;
}
mPaint.setTextSize(mTextSize);
mPaint.setTextAlign(Paint.Align.CENTER);
mPaint.setStrokeWidth(mLinesWidth);
//draw background
mPaint.setColor(mBackColor);
canvas.drawRect(0, 0, mWidth, mHeight, mPaint);
mPaint.setColor(mHeightBackColor);
canvas.drawRect(0, mAreaHeight, mWidth, mAreaHeight + mSpaceHeight, mPaint);
canvas.drawRect(0, mAreaHeight * 2 + mSpaceHeight, mWidth, mHeight, mPaint);
if (mMileList == null || mMileList.size() == 0) {
// TODO: 16/2/18 paint something when there's no data
} else {
// int listSize = mMileList.size();
// if (mMileList.size() < LIST_SIZE + 1) {
// mLess14 = true;
// mSpaceLength = (mWidth - mDataLength * listSize) / (listSize + 1);
// mLength = 0;
// mLeftPosition = 0;
// } else {
// mLess14 = false;
// listSize = LIST_SIZE;
// mMaxLeftPosition = mMileList.size() - listSize;
// }
mPaint.setColor(mBarBackColor);
for (int i = 0; i < mDataSize; ++i) {
canvas.drawRect(mLength + mSpaceLength + i * (mSpaceLength + mDataLength), 0, mLength + (i + 1) * (mSpaceLength + mDataLength), mAreaHeight, mPaint);
canvas.drawRect(mLength + mSpaceLength + i * (mSpaceLength + mDataLength), mAreaHeight + mSpaceHeight, mLength + (i + 1) * (mSpaceLength + mDataLength), mAreaHeight * 2 + mSpaceHeight, mPaint);
}
//draw line
mPaint.setColor(mSpeedColor);
float maxSpeed = 0;
// float x = 0;
// float y = 0;
float xx;
float yy;
int size = mSpeedList.size();
for (int i = 0; i < size; ++i) {
if (maxSpeed < mSpeedList.get(i)) {
maxSpeed = mSpeedList.get(i);
}
}
for (int i = 0; i < mDataSize; ++i) {
xx = mLength + mSpaceLength + i * (mSpaceLength + mDataLength) + mDataLength / 2;
yy = mAreaHeight / 6 + (1 - mSpeedList.get(mLeftPosition + i) / maxSpeed) * mAreaHeight / 6 * 5;
canvas.drawCircle(xx, yy, 8, mPaint);
canvas.drawText(mDf.format(mSpeedList.get(mLeftPosition + i)), xx, yy - mAreaHeight / 48 * 3, mPaint);
// if (i == mDataSize - 1) {
// canvas.drawText("速度(m/s)", xx + mDataLength, yy + mAreaHeight / 24, mPaint);
// }
}
canvas.drawText("item1", (mWidth - mScreenWidth) / 2 + mSpaceLength + 10, 40, mPaint);
mPaint.setStyle(Paint.Style.STROKE);
mLinePath.reset();
mPointsX.clear();
mPointsY.clear();
if(mUseConner) {
for (int i = 0; i < mDataSize; ++i) {
xx = mLength + mSpaceLength + i * (mSpaceLength + mDataLength) + mDataLength / 2;
yy = mAreaHeight / 6 + (1 - mSpeedList.get(mLeftPosition + i) / maxSpeed) * mAreaHeight / 6 * 5;
mPointsX.add((int) xx);
mPointsY.add((int) yy);
}
drawCurve(canvas, mPointsX, mPointsY, mLinePath, mPaint);
} else {
for (int i = 0; i < mDataSize; ++i) {
xx = mLength + mSpaceLength + i * (mSpaceLength + mDataLength) + mDataLength / 2;
yy = mAreaHeight / 6 + (1 - mSpeedList.get(mLeftPosition + i) / maxSpeed) * mAreaHeight / 6 * 5;
if (i == 0) {
mLinePath.moveTo(xx, yy);
} else {
mLinePath.lineTo(xx, yy);
}
}
canvas.drawPath(mLinePath, mPaint);
}
mPaint.setStyle(Paint.Style.FILL);
mPaint.setColor(mHeartColor);
float maxHeart = 0;
size = mHeartList.size();
for (int i = 0; i < size; ++i) {
if (maxHeart < mHeartList.get(i)) {
maxHeart = mHeartList.get(i);
}
}
for (int i = 0; i < mDataSize; ++i) {
xx = mLength + mSpaceLength + i * (mSpaceLength + mDataLength) + mDataLength / 2;
yy = mAreaHeight / 6 + (1 - mHeartList.get(mLeftPosition + i) / maxHeart) * mAreaHeight / 6 * 5;
canvas.drawCircle(xx, yy, 8, mPaint);
canvas.drawText(mHeartList.get(mLeftPosition + i) + "", xx, yy - mAreaHeight / 48 * 3, mPaint);
// if (i == mDataSize - 1) {
// canvas.drawText("心率(t/min)", xx + mDataLength, yy + mAreaHeight / 24, mPaint);
// }
}
canvas.drawText("item2", (mWidth - mScreenWidth) / 2 + mSpaceLength + 10, 80, mPaint);
3.onTouchEvent的实现
float distance = 0; //移动距离
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mX = event.getX();
break;
case MotionEvent.ACTION_MOVE:
if (mX == 0) {
mX = event.getX();
}
//根据数据大小会有不同处理
if (mLess14) {
//小于等于14时处理很简单
// move right
if (mX - event.getX() < 0) {
distance = (int) Math.abs(event.getX() - mX);
mScreenPosition -= distance;
if (mScreenPosition < 0) {
mScreenPosition = 0;
}
this.scrollTo((int) mScreenPosition, 0);
}
// move left
else if (mX - event.getX() > 0)
{
distance = (int) Math.abs(event.getX() - mX);
mScreenPosition += distance;
if (mScreenPosition > mWidth - mScreenWidth) {
mScreenPosition = mWidth - mScreenWidth;
}
this.scrollTo((int) mScreenPosition, 0);
}
} else {
//大于14的情况就复杂些了,向右滑动,左滑同理
// move right
if (mX - event.getX() < 0) {
distance = (int) Math.abs(event.getX() - mX);
// 还没有滑到最右边
if (mLeftPosition != 0) {
mLength += distance;
//当mLength大于mSpaceLength+mDataLength时,将其归0,并将mLeftPosition减小1,
//这样重绘后相当于滑动到了下一正确位置,如果不这样处理,我们的数据展示就出错了
if (mLength > mSpaceLength + mDataLength) {
mLength = 0;
if (mLeftPosition > 0) {
--mLeftPosition;
}
}
}
// 滑到最右边了
else{
mLength += distance;
if (mLength > 0) {
mLength = 0;
}
}
this.postInvalidate();
}
// move left
else if (mX - event.getX() > 0) {
distance = (int) Math.abs(event.getX() - mX);
if (mLeftPosition != mMaxLeftPosition) {
mLength -= distance;
if (mLength < -mDataLength - mSpaceLength) {
mLength = 0;
if (mLeftPosition < mMaxLeftPosition) {
++mLeftPosition;
}
}
} else {
mLength -= distance;
if (mLength < -(mSpaceLength + mDataLength) * 2 - mSpaceLength) {
mLength = -(mSpaceLength + mDataLength) * 2 - mSpaceLength;
}
}
this.postInvalidate();
}
}
mX = event.getX();
break;
case MotionEvent.ACTION_UP:
mX = 0;
break;
default:
break;
}
return true;