android view canvas 绘制导致的闪屏问题发现及解决

最近在做一个自定义进度条,为了能实现任意多边形的进度条,以及是否包含中心点位置。等各种骚操作。我使用了不少的 Path.Op的操作。只是显示的时候并没有什么毛病。但是调用进度更新的时候,界面会闪屏。

看一下效果先:

就是这样子在调用更新的时候一直闪烁

调用更新的代码如下:

final RoundProgressBar bar = findViewById(R.id.progress_bar);

        bar.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                LogUtils.i("click");
                bar.setClickable(false);
                ValueAnimator animator = ValueAnimator.ofInt(0, 100);
                animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                    @Override
                    public void onAnimationUpdate(ValueAnimator animation) {
                        int value = (Integer) animation.getAnimatedValue();
                        bar.setProgress((int) value);
                        LogUtils.e("value----" + value + " 0000000 ");
                        if (bar.getProgress() >= 100) {
                            animation.cancel();
                            bar.setClickable(true);
                        }
                    }
                });
                animator.setDuration(2000);
                animator.start();
            }
        });

更新的代码看起来没什么毛病。但是界面就是像上面一下一直闪。

我怀疑是 onDraw 里面的逻辑太多导致的,然后尝试使用了双缓冲,就是先绘制到一个bitmap上面,然后使用 canvas.drawBitmap来绘制这个bitmap到界面上。

不过并没有什么用,依然闪烁。

最终,我找到了黄猫。然后黄猫看了半天没发现什么毛病,最后告诉我,我的 path在绘制之前没有 reset,导致后续绘制的时候,path会一直叠加。我感觉很有道理。然后绘制之前先重置一下,果然没毛病了。

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        resetAllPathsBeforeDraw();
        canvas.drawRect(0, 0, mWidth, mHeight, textPaint); // for debug
        int centerX = (getPaddingLeft() + getWidth() - getPaddingRight()) / 2;
        int centerY = (getPaddingTop() + getHeight() - getPaddingBottom()) / 2;
        int radius = Math.min(
                getWidth() - getPaddingLeft() - getPaddingRight(),
                getHeight() - getPaddingTop() - getPaddingBottom()
        ) / 2;
        canvas.rotate(mStartAngle, centerX, centerY);
        drawShape(canvas, centerX, centerY, radius);
        drawProgress(canvas, centerX, centerY, radius);
        drawSecondaryProgress(canvas, centerX, centerY, radius);
    }

    /**
     * 闪烁的根源找到了, path 过度叠加导致。每次先 reset 即可!
     */
    private void resetAllPathsBeforeDraw() {
        if (outerShapePath != null) {
            outerShapePath.reset();
        }
        if (innerShapePath != null) {
            innerShapePath.reset();
        }
        if (progressPath != null) {
            progressPath.reset();
        }
        if (secondaryProgressPath != null) {
            secondaryProgressPath.reset();
        }
    }

看一下最终效果:

不闪烁的效果
没毛病。

好了,各位老铁,今天关于闪烁的问题就说到这里了。

从这里也能看出,view 的闪烁往往都是操作太重导致的。比如这里,path会一直叠加在更新到时候,导致进度条越更新,path里面包含的内容越多。最终导致闪烁了。

完整代码其实比较简单,不过喜欢的老铁可以 双击666 查看。

然后,为了让这个进度条更6 ,我决定增加一个点击上面就让进度 +5%,点击下面就让进度-5%,向上滑动就慢慢加进度,向下滑动就慢慢减进度的骚操作。

核心逻辑:

            case MotionEvent.ACTION_MOVE: {
                float y = event.getY();
                float x = event.getX();
                float diffX = x - eventX;
                float diffY = y - eventY;
                // LogUtils.d("dx=%.1f,dy=%.1f,ts=%d", diffX, diffY, touchSlop);
                if (/*Math.abs(diffX) < touchSlop && */Math.abs(diffY) >= touchSlop) {
                    // 默认滑太多,搞个 0.25 缓冲一下
                    float factor = (-1f) * diffY / mHeight * 0.25f; // 预期从中间滑到顶部,为 +50%
                    // 系统坐标决定下滑时 diffY 为正值,所以前面加 (-1f)
                    mProgress += factor * mMax;
                    // LogUtils.i("factor=%s,progress=%s", factor, mProgress);
                    mProgress = mProgress > mMax ? mMax : mProgress;
                    mProgress = mProgress < 0 ? 0 : mProgress;
                    setProgress(mProgress);
                }
            }
            break;
            case MotionEvent.ACTION_UP: {
                // LogUtils.e("#ACTION_UP#ACTION_UPACTION_UP#ACTION_UP#ACTION_UP");
                float y = event.getY();
                float x = event.getX();
                float diffX = x - eventX;
                float diffY = y - eventY;
                long elapsedRealtime = SystemClock.elapsedRealtime() - eventTime;
                boolean noMove = Math.abs(diffX) < touchSlop && Math.abs(diffY) < touchSlop;
                if (noMove && elapsedRealtime > longPressTimeout) {
                    // 超过此时间就认为是长按,认为是一次长按
                    performLongClick();
                    // 长按不进行 进度的累加
                } else if (noMove && elapsedRealtime < tapTimeout) {
                    // 小于此时间,认为是一次点击
                    if (y < mHeight / 2) {
                        // 点击上边,让进度 + 5 %
                        mProgress += 0.05 * mMax;
                    } else {
                        // 点击下边,让进度 - 5 %
                        mProgress -= 0.05 * mMax;
                    }
                    mProgress = mProgress > mMax ? mMax : mProgress;
                    mProgress = mProgress < 0 ? 0 : mProgress;
                    setProgress(mProgress);
                    performClick();
                }
            }
            break;

效果如下:

在这里插入图片描述
吐槽一下,录屏之后看不到手指触摸的效果了。明明已经打开了触摸效果显示。

然后,其实这个进度条可以设置任意多边形的,以及是否填满。比如三角形:

在这里插入图片描述

感觉还行的老铁,记得 双击666

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值