一、简介
最近产品经理总想试一些新鲜东西,于是需求中就添加了一些自定义控件。好久没写过自定义控件了,之前道长也写过一些自定义控件,详见:《属性动画:如何自定义View》、《自定义View:自定义CircleImageView实现及图形渲染》、《自定义View:用Canvas实现转盘View》等。这次有几个转轮的变形,其实这几个自定义控件的实现方式是相似的,其他不说先上图,如下所示:
二、实现
由于这三个控件的实现是相似的,也就不一一详说了,就说其中道长遇到的问题。
2.1 每个单元的滑动计算以及重绘
- 首先定义柱状图的初始位置
这三个控件的初始位置都是屏幕中间,所以需要把初始位置定义为屏幕的二分之一,这里贴的FoodCalView的代码,如下所示:
if (!setStart) {
setStart = false;
startOriganalX = measureWidth / 2;
}
又因为这三个空间的左滑右滑不一致,所以对第一个单元的位置矫正也不同,如下所示:
if (startOriganalX == 0) {
startOriganalX = measureWidth / 2;
}
int startX = (int) (paddingLeft + startOriganalX - barWidth / 2);
注意:在滚轮滑动期间,界面会随着滑动而更新界面,如果处理不好就会一直在初始位置鬼畜。
- 把每个柱状图的bar和间隔定义为一个单元,滑动时计算出当前单元、滑动方式等
if ((currX - lastX) < 0) {
Log.e("yushan", "向左滑动");
if (startOriganalX < measureWidth / 2) {
startOriganalX = measureWidth / 2;
isBoundary = true;
}
moveTo = "Left";
centerPosition = (int) (startOriganalX - measureWidth / 2 + barInterval) / (barWidth + barInterval);
nextDis = (startOriganalX - measureWidth / 2) % (barWidth + barInterval);
} else {
if (startOriganalX > measureWidth / 2 + (barWidth + barInterval) * (innerData.size() - 1)) {
startOriganalX = measureWidth / 2 + (barWidth + barInterval) * (innerData.size() - 1);
isBoundary = true;
}
Log.e("yushan", "向右滑动");
moveTo = "Right";
centerPosition = (int) (startOriganalX - measureWidth / 2 + barWidth) / (barWidth + barInterval);
nextDis = (startOriganalX - measureWidth / 2) % (barWidth + barInterval);
}
注意:如果计算不正确,用户体验非常差
- 在手指抬起时,计算滑动速度和处理
在手指抬起时,要计算手指抬起时移动的速度。当手指移动速度在100~1000之间,并且填充的数据大于屏幕宽度时,逐渐停止移动。
当移动完毕时,如果柱状图bar不在屏幕中间,则根据nextDis让邻近的柱状图移动到屏幕中间。代码如下所示:
case MotionEvent.ACTION_UP:
long endTime = System.currentTimeMillis();
//计算猛滑动的速度,如果是大于某个值,并且数据的长度大于整个屏幕的长度,那么就允许有flIng后逐渐停止的效果
float speed = tempLength / (endTime - startTime) * 1000;
if (Math.abs(speed) > 100 && Math.abs(speed) < 1000 && !isFling && measureWidth < innerData.size() * (barWidth + barInterval)) {
this.post(horizontalScrollRunnable = new HorizontalScrollRunnable(speed));
} else if (nextDis > 0) {
this.post(scroll2CenterRunnable = new Scroll2CenterRunnable(nextDis));
}
isMove = false;
break;
- WeightWheelView的绘制要求在手指移动时刻度尺和拳头的移动填充要实时更新,由于拳头是不规则形状,道长绘制的时候废了不少心思,效果也不错,绘制拳头的代码如下:
private Bitmap drawFistImage() {
Paint paint = new Paint();
paint.setAntiAlias(true);
Bitmap finalBmp = Bitmap.createBitmap(middleBitmap.getWidth(), middleBitmap.getHeight(), Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(finalBmp);
canvas.drawBitmap(backgroundBitmap, 0, 0, paint);
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_ATOP));
canvas.drawBitmap(middleBitmap, 0, 0, paint);
return finalBmp;
}
- 在最后说一下,在手指移动时屏幕重绘,重绘的画布会越来越长,然后控件会越来越卡,由于任务时间紧急,这里道长只是简单处理了一下,如下所示:
if (centerPosition % 15 == 0 && centerPosition != 0) {
refreshList.clear();
if (innerData.size() > 20) {
refreshList.addAll(innerData.subList(0, centerPosition + 20));
} else {
refreshList.addAll(innerData);
}
}
关于滚轮绘制,道长暂时说道这里,由于时间紧迫,难免会有问题,希望小伙伴可以指出。如果需要参考一下的小伙伴,可以点击下面的传送门:
附加:
临近年末,公司打算对这个功能做全盘优化,之前有小伙伴说这个自定义控件滑动卡顿,道长抽空对这几个控件做了优化。其实细心地小伙伴发现道长的这个Demo中的三个自定义控件实现方式是相近的,就是相应的变种而已,主要是在OnDraw做一下算法而已,第一个控件具体的OnDraw变动如下:
for (int i = centerPosition - 50; i < centerPosition + 50; i++) {
if (i >= innerData.size() || i < 0) {
continue;
}
float barHeight = 0;
if (scaleTimes != 0) {
float barValue;
if (innerData.get(i).getCount() > maxValue * 2) {
barValue = maxValue * 2;
} else {
barValue = innerData.get(i).getCount();
}
barHeight = barValue / scaleTimes;
}
int startY = (int) (defaultHeight - bottom_view_height - barHeight);
//绘制下面的文字
float bottomTextWidth = mTopTextPaint.measureText(innerData.get(i).date);
float bottomStartX = startX + barWidth / 2 - bottomTextWidth / 2 - i * (barWidth + barInterval);
Rect rect = new Rect();
mTopTextPaint.getTextBounds(innerData.get(i).getDate(), 0, innerData.get(i).getDate().length(), rect);
float bottomStartY = defaultHeight - bottom_view_height + 10 + rect.height();//rect.height()是获取文本的高度;
//绘制线
drawBottomLine(canvas, startX + barWidth / 2 - i * (barWidth + barInterval), bottomStartY);
if (innerData.get(i).count != 0) {
//绘制bar
if (i == centerPosition) {
drawCenterBar(canvas, startX - i * (barWidth + barInterval), startY + dp2Px(30), endY);
} else {
drawBar(canvas, startX - i * (barWidth + barInterval), startY + dp2Px(30), endY);
}
}
//绘制底部的文字
drawTopText(canvas, innerData.get(i).getDate(), bottomStartX, bottomStartY);
}
第二个控件OnDraw代码变动如下:
for (int i = centerPosition - 50; i < centerPosition + 50; i++) {
if (i >= refreshList.size() || i < 0) {
continue;
}
float barHeight = 0;
int startY = (int) (defaultHeight - bottom_view_height - barHeight);
if (i == 0) {
drawText = "今日";
} else {
drawText = refreshList.get(i).getDate();
}
//绘制下面的文字
float bottomTextWidth = mTopTextPaint.measureText(drawText);
float bottomStartX = startX + barWidth / 2 - bottomTextWidth / 2 - (barWidth + barInterval) * i;
Rect rect = new Rect();
mTopTextPaint.getTextBounds(refreshList.get(i).getDate(), 0, refreshList.get(i).getDate().length(), rect);
float bottomStartY = defaultHeight - bottom_view_height + 10 + rect.height();//rect.height()是获取文本的高度;
//绘制底部的文字
drawText(canvas, drawText, bottomStartX, bottomStartY);
}
重点说一下第三个控件,因为前两个控件就是在OnDraw代码有了变动,其他没有变动。而且第三个控件的全部代码也会贴在最后。
- 在OnDraw中的for循环由之前的循环加载所有数据改为了加载数据段,数据段的长度为200,这个可以改动。代码如下:
@Override
protected void onDraw(Canvas canvas) {
if (setStart) {
setStart = false;
startOriganalX = measureWidth / 2 - barWidth / 2 - startChangeX;
}
if (startOriganalX =