Android自定义View自定义View

Android自定义View实现可拖拽的进度条

2021-09-01  本文已影响0人  itfitness

目录

效果展示

实现步骤

1.计算出控件宽度的直线路径

在onSizeChanged方法中进行计算,这时可以得到一条与控件宽度相同的直线,并把路径设置给PathMeasure

 @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        //进度条绘制在控件中央,宽度为控件宽度(mProgressHeight/2是为了显示出左右两边的圆角)
        mPathProgressBg.moveTo(mProgressHeight / 2,h / 2f);
        mPathProgressBg.lineTo(w - mProgressHeight / 2,h / 2f);
        //将进度条路径设置给PathMeasure
        mPathMeasure.setPath(mPathProgressBg,false);
        invalidate();
    }
2.计算当前进度的路径

使用PathMeasure得出当前进度的路径并进行绘制,这里我将上一步的绘制放在了一起

private void drawProgress(Canvas canvas) {
        mPathProgressFg.reset();
        mPaintProgress.setColor(mColorProgressBg);
        //绘制进度背景
        canvas.drawPath(mPathProgressBg, mPaintProgress);
        //计算进度条的进度
        float stop = mPathMeasure.getLength() * mProgress;
        //得到与进度对应的路径
        mPathMeasure.getSegment(0,stop,mPathProgressFg,true);
        mPaintProgress.setColor(mColorProgressFg);
        //绘制进度
        canvas.drawPath(mPathProgressFg, mPaintProgress);
    }
3.计算显示进度的圆角矩形

这个矩形的宽度需要我们用绘制最长的文字来确定其宽高



另外矩形的显示位置也是以当前进度所在的点为中心点


private void drawShowProgressRoundRect(Canvas canvas) {
        float stop = mPathMeasure.getLength() * mProgress;//计算进度条的进度
        //根据要绘制的文字的最大长宽来计算要绘制的圆角矩形的长宽
        Rect rect = new Rect();
        mPaintProgressText.getTextBounds(mProgressMaxText,0, mProgressMaxText.length(),rect);
        //要绘制矩形的宽、高
        float rectWidth = rect.width() + (mProgressStrMarginH * 2);
        float rectHeight = rect.height() + (mProgressStrMarginV * 2);
        //计算边界值(为了不让矩形在左右两边超出边界)
        if(stop < rectWidth / 2f){
            stop = rectWidth / 2f;
        }else if(stop > (getWidth() - rectWidth / 2f)){
            stop = getWidth() - rectWidth / 2f;
        }
        //定义绘制的矩形
        float left = stop - rectWidth / 2f;
        float right = stop + rectWidth / 2f;
        float top = getHeight() / 2f - rectHeight / 2f;
        float bottom = getHeight() / 2f + rectHeight / 2f;
        mProgressRoundRectF = new RectF(left,top,right,bottom);
        //绘制为圆角矩形
        canvas.drawRoundRect(mProgressRoundRectF, mRoundRectRadius, mRoundRectRadius,mPaintRoundRect);
    }
4.计算文字的显示位置

文字显示的位置计算起来就比较简单了,直接用上一步算出的矩形的中心点即可,不过这里需要调整文字绘制的垂直的偏移,这样才能实现文字垂直居中

private void drawProgressText(Canvas canvas) {
        String progressText = (int)Math.floor(100 * mProgress) + "%";
        //让文字垂直居中的偏移
        int offsetY = (mFontMetricsInt.bottom - mFontMetricsInt.ascent) / 2 - mFontMetricsInt.bottom;
        //将文字绘制在矩形的中央
        canvas.drawText(progressText,mProgressRoundRectF.centerX(),mProgressRoundRectF.centerY() + offsetY,mPaintProgressText);
    }
5.实现拖拽

实现拖拽需要对onTouchEvent方法进行处理,也就是当手指触摸矩形区域的时候,根据手指横向滑动的偏移来设置当前的进度,具体如下

@Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()){
            case MotionEvent.ACTION_DOWN:
                //判断手指是否触摸了显示进度的圆角矩形块,这样才可以拖拽
                if(mProgressRoundRectF != null && mProgressRoundRectF.contains(event.getX(),event.getY())){
                    //记录手指刚接触屏幕的X轴坐标(因为只需要在X轴上平移)
                    mStartTouchX = event.getX();
                    mIsTouchSeek = true;
                }
                break;
            case MotionEvent.ACTION_MOVE:
                if(mIsTouchSeek){
                    //计算横向移动的距离
                    float moveX = event.getX() - mStartTouchX;
                    //计算出当前进度的X轴所显示的进度长度
                    float currentProgressWidth = mPathMeasure.getLength() * mProgress;//计算进度条的进度
                    //计算滑动后的X轴的坐标
                    float showProgressWidth = currentProgressWidth + moveX;
                    //计算边界值
                    if(showProgressWidth < 0){
                        showProgressWidth = 0;
                    }else if(showProgressWidth > mPathMeasure.getLength()){
                        showProgressWidth = mPathMeasure.getLength();
                    }
                    //计算滑动后的进度
                    mProgress = showProgressWidth / mPathMeasure.getLength();
                    //重绘
                    invalidate();
                    //刷新用于计算移动的X轴坐标
                    mStartTouchX = event.getX();
                }
                break;
            case MotionEvent.ACTION_UP:
                mIsTouchSeek = false;
                break;
        }
        return mIsTouchSeek;
    }
6.计算当前自定义View的宽高

为了适配高度的wrap_content属性,我们需要计算出控件最小需要显示的高度



这里我们是用显示进度的矩形的高度作为控件最小的高度的,因为矩形的高度是所有图形最高的一个



而矩形的高度又是文字的大小与边距之和
 @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(measureSizeWidth(widthMeasureSpec), measureSizeHeight(heightMeasureSpec));
    }

    //计算宽度
    private int measureSizeWidth(int size) {
        int mode = MeasureSpec.getMode(size);
        int s = MeasureSpec.getSize(size);
        if (mode == MeasureSpec.EXACTLY) {
            return s;
        } else{
            return Math.min(s, 200);
        }
    }
    //计算高度
    private int measureSizeHeight(int size) {
        int mode = MeasureSpec.getMode(size);
        int s = MeasureSpec.getSize(size);
        if (mode == MeasureSpec.EXACTLY) {
            return s;
        }else {
            //自适应模式,返回所需的最小高度
            return (int) (mTextSize + mProgressStrMarginV * 2);
        }
    }

案例源码

https://gitee.com/itfitness/seek-progress-bar

上一篇下一篇

猜你喜欢

热点阅读