自定义控件:玩转滚轮(柱状图、日期滚轮、刻度尺)

一、简介

最近产品经理总想试一些新鲜东西,于是需求中就添加了一些自定义控件。好久没写过自定义控件了,之前道长也写过一些自定义控件,详见:《属性动画:如何自定义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);
                    }
                }

关于滚轮绘制,道长暂时说道这里,由于时间紧迫,难免会有问题,希望小伙伴可以指出。如果需要参考一下的小伙伴,可以点击下面的传送门:

WheelDemo

附加:
临近年末,公司打算对这个功能做全盘优化,之前有小伙伴说这个自定义控件滑动卡顿,道长抽空对这几个控件做了优化。其实细心地小伙伴发现道长的这个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 =
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值