美文网首页Android自定义View
从头开始写一个带分割圆点和小球回弹效果的progressbar

从头开始写一个带分割圆点和小球回弹效果的progressbar

作者: 暴走的小青春 | 来源:发表于2019-06-02 20:43 被阅读108次

    想必原生的progressbar大家都很熟悉,但是最近做项目要实现一个带有分割原点的和小球回弹的progressbar,实现效果如下:

    circledemo.gif

    可以看到是一个很基础的自定义控件,但是确很常用,一般用在用户选择应用的自动关闭时间,字体大小等
    那下面就来具体分析下:

    首先说下思路:

    1.绘制viewgroup也就是后面的灰色的背景点和线。
    2.新建view也就是拖动的小球,通过重写view的ontouchevent从而实现小球的移动以及回弹效果。
    3.通过重写viewgroup的ontouchevent从而实现点击后面的小圆点,让小球到此位置
    4.通过重写viewgroup的ondraw方法,从而画出跟随小球的进度条

    1:画线和点

    由于继承了framelayout不用考虑layoutparams和onlayout方法
    故只要在viewgroup的onmeasure方法里

    @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    
            measureWidth = MeasureSpec.getSize(widthMeasureSpec);
            measureHeight = MeasureSpec.getSize(heightMeasureSpec);
            int heightMode = MeasureSpec.getMode(heightMeasureSpec);
    
            if (heightMode == MeasureSpec.AT_MOST) {
                measureHeight = dpToPx(20);
            }
            heightMeasureSpec = MeasureSpec.makeMeasureSpec(measureHeight, MeasureSpec.EXACTLY);
            mOvalRadius = resetRadius == -1 ? measureHeight / 2 : resetRadius;
    
            int eachWidth = (measureWidth - 2 * mOvalRadius) / internalNumber;
            if (eachWidth > 0) {
                int totalPaintWidth = 0;
                mAllintervalPoints.clear();
                while (totalPaintWidth <= measureWidth) {
                    mAllintervalPoints.add(new Point(totalPaintWidth, measureHeight / 2));
                    totalPaintWidth = totalPaintWidth + eachWidth;
    
                }
            }
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        }
    

    这里通过在viewgroup的onmeausre前用了for循环算出了间隔点的每个位置,从而加到了AllintervalPoints里,也就是他的子view能获取到这个point的list
    然后很明显对wrap_content的情况做了支持。
    然后看下ondraw方法:

    @Override
        protected void onDraw(Canvas canvas) {
            float halfHeight = measureHeight / 2;
            int eachWidth = (measureWidth - 2 * mOvalRadius) / internalNumber;
            canvas.save();
            canvas.translate(mOvalRadius, 0);
            int totalPaintWidth = 0;
    
    
            while (totalPaintWidth <= measureWidth) {
                //canvas.drawLine(totalPaintWidth, -halfHeight / 2, totalPaintWidth, halfHeight / 2, mBackGroundPaint);
                canvas.drawCircle(totalPaintWidth, halfHeight, halfHeight / 2, mBackGroundPaint);
                totalPaintWidth = totalPaintWidth + eachWidth;
    
            }
            canvas.restore();
            canvas.drawLine(mOvalRadius, measureHeight / 2, measureWidth - mOvalRadius, measureHeight / 2, mBackGroundPaint);
           
    

    在ondraw里画出了线以及各个小圆点。

    2:新建并拖动小球

    首先在小球的onmeasure中

     @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
            if (getParent() instanceof SeekBarView) {
                seekBarView = (SeekBarView) getParent();
            } else {
                throw new RuntimeException("this view parent must be seekBarView");
            }
            measureHeight = getMeasuredHeight();
    
            try {
                mOvalRadius = seekBarView.mOvalRadius;
                mAllintervalPoints = seekBarView.mAllintervalPoints;
                centerX = (mAllintervalPoints.get(1).x - mAllintervalPoints.get(0).x) / 2;
                ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) getLayoutParams();
                params.setMargins(mAllintervalPoints.get(1).x, 0, 0, 0);
                setLayoutParams(params);
             
                Log.i("CircleView", measureHeight + "");
            } catch (Exception e) {
                e.printStackTrace();
            }
            setMeasuredDimension(measureHeight, measureHeight);
    
    
        }
    

    这里对小球的宽度做了重新的measure,因为父view传过来的宽度是父view的,所以重新设置了下,并做了容错。
    然后重写了onTouchEvent方法

     @Override
        public boolean onTouchEvent(MotionEvent event) {
            int lastX = 0;
            int x = (int) event.getX();
            switch (event.getAction()) {
                case MotionEvent.ACTION_DOWN:
                    Log.i("CircleView", getRight() + "");
                    lastX = x;
                    break;
                case MotionEvent.ACTION_MOVE:
                    int offsetX = x - lastX;
                    if (getLeft() + offsetX >= 0 && getRight() + offsetX <= ((ViewGroup) getParent()).getMeasuredWidth()) {
                        ViewCompat.offsetLeftAndRight(this, offsetX);
                    }
                    break;
                case MotionEvent.ACTION_UP:
                case MotionEvent.ACTION_CANCEL:
                    int finalX = getLeft();
                    for (int i = 0; i < mAllintervalPoints.size(); i++) {
                        if (mAllintervalPoints.get(i).x >= finalX) {
                            try {
                                if (mAllintervalPoints.get(i - 1).x + centerX >= finalX) {
                                    layout(mAllintervalPoints.get(i - 1).x, getTop(), mAllintervalPoints.get(i - 1).x + 2 * mOvalRadius, getBottom());
                                    
                                    dispatchListener(i - 1);
                                    break;
    
                                } else {
                                    layout(mAllintervalPoints.get(i).x, getTop(), mAllintervalPoints.get(i).x + 2 * mOvalRadius, getBottom());
                                    
                                    dispatchListener(i);
                                    break;
                                }
                            } catch (Exception e) {
                                layout(mAllintervalPoints.get(i).x, getTop(), mAllintervalPoints.get(i).x + 2 * mOvalRadius, getBottom());
                                
                                dispatchListener(i);
                                break;
                            }
                        }
                    }
    
    
                    break;
            }
            return true;
        }
    

    在小球的ontouchevent里利用 ViewCompat.offsetLeftAndRight这个滑动是改变view的layout的不同于scroller以及属性动画从而能改变getleft()的值。

    3.点击小圆点,移动小球

     @Override
        public boolean onTouchEvent(MotionEvent event) {
            int lastX = 0;
            int x = (int) event.getX();
            int y = (int) event.getY();
            switch (event.getAction()) {
                case MotionEvent.ACTION_DOWN:
                    lastX = x;
                    break;
                case MotionEvent.ACTION_MOVE:
                    break;
                case MotionEvent.ACTION_UP:
                case MotionEvent.ACTION_CANCEL:
                    if ((lastX - x) < Math.abs(defaultMinInternal)) {
    
                        Point finalPoint = isControlPoint(x, y);
                        if (finalPoint != null) {
                            if (getChildAt(0) instanceof CircleView) {
                                CircleView circleView = (CircleView) getChildAt(0);
                                circleView.simulateScroll(finalPoint);
                            } else {
                                throw new RuntimeException("the seekBarView must be one child");
                            }
                        }
                    }
    
                    break;
            }
            return true;
    
        }
    

    这里首先判定是click,然后判断点击区域是否在几个小点的范围内

     /**
        * 判断点击区域
        */
       private Point isControlPoint(float x, float y) {
           for (Point controlPoint : mAllintervalPoints) {
               RectF pointRange = new RectF(controlPoint.x - defaultRectNumber,
                       controlPoint.y - defaultRectNumber,
                       controlPoint.x + defaultRectNumber,
                       controlPoint.y + defaultRectNumber);
               // 如果包含了就,返回true
               if (pointRange.contains(x, y)) {
                   return controlPoint;
               }
    
           }
           return null;
       }
    

    新建一个rect判定范围,然后传给小球具体的位置从而让小球layout发证改变。

    4.画进度条

    同样是通过viewgroup的ondraw方法

     @Override
        protected void onDraw(Canvas canvas) {
            float halfHeight = measureHeight / 2;
            int eachWidth = (measureWidth - 2 * mOvalRadius) / internalNumber;
            canvas.save();
            canvas.translate(mOvalRadius, 0);
            int progressPaintWidth = 0;
    
            for (int i = 0; i < mAllintervalPoints.size(); i++) {
                if (progressPaintWidth != -1 && mAllintervalPoints.get(i).x >= progressMeasureLine) {
                    try {
                        progressMeasureWidth = mAllintervalPoints.get(i - 1).x;
                        break;
                    } catch (Exception e) {
                        progressMeasureWidth = mAllintervalPoints.get(i).x;
                    }
    
                }
            }
            while (progressPaintWidth <= progressMeasureWidth) {
                //canvas.drawLine(totalPaintWidth, -halfHeight / 2, totalPaintWidth, halfHeight / 2, mBackGroundPaint);
                canvas.drawCircle(progressPaintWidth, halfHeight, halfHeight / 2, mProgressPaint);
                progressPaintWidth = progressPaintWidth + eachWidth;
    
            }
            canvas.restore();
            canvas.drawLine(mOvalRadius, measureHeight / 2, progressMeasureLine + mOvalRadius, measureHeight / 2, mProgressPaint);
    
        }
    

    这里算出了小球getleft()的距离从而更新了进度条。

    最后还提供了setIntervalNumber和setOvalRadius方法,当然以后可能还会做更大的扩展。
    github地址:https://github.com/jhonsonkilly/scrollcircledemo
    欢迎star!!!

    相关文章

      网友评论

        本文标题:从头开始写一个带分割圆点和小球回弹效果的progressbar

        本文链接:https://www.haomeiwen.com/subject/bjcfxctx.html