Android自定义SeekBar(带数字显示跟着变化)

首先看一下最后实现的效果图

Android自定义SeekBar(带数字显示跟着变化)_第1张图片
seekbar.png


可左右滑动,点击加减按钮动态改变数值和位置显示。

思路:

1.根据图上UI知道有三种状态

  • 未滑中区域的灰色底部状态

  • 滑中的颜色改变状态

  • 椭圆的边框颜色,和内部字体显示
  • 实现:

    首先我们自定义一个类MySeekBar继承View。并实现最重要的onDraw,onMeasure方法。
    1.首先定义一个样式,用来以后设置底部颜色,滑中颜色,字体大小,颜色等

    资源样式
        
             滑动时候横条颜色
            默认的横条颜色
            椭圆边框颜色
            判断是按年份显示还是月份显示
            获得textview显示的后缀
        
    

    2.实现MySeekBar构造器。初始化资源,与需要的画笔。(注意获得资源后一定要使用recycle(),去进行回收)

      public MySeekBar(Context context, AttributeSet attrs) {
            super(context, attrs);
            TypedArray attr = getTypedArray(context, attrs, R.styleable.MySeekBar);
            mTopBarColor = attr.getColor(R.styleable.MySeekBar_mTopBarColor,     getResources().getColor(R.color.color_EB7046));//设置滑动时候横条颜色
            mBottomBarColor = attr.getColor(R.styleable.MySeekBar_mBottomBarColor, getResources().getColor(R.color.login_font));//默认的横条颜色
            month = attr.getBoolean(R.styleable.MySeekBar_month, false);//判断是按年份显示还是月份显示
            myText = attr.getString(R.styleable.MySeekBar_mytext);//获得textview显示的后缀
            mContext = context;
            Data(Calendar.getInstance());//得到当前的日期
            windows_width = DisplayMetricsTool.getWidth(mContext);//获得屏幕宽度用来适配各种屏幕
            dip_size = DisplayMetricsTool.dip2px(mContext, 75);
            //矩形的白色底部画笔
            mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
            paint = new Paint(); //设置一个笔刷大小是3的黄色的画笔
            paint.setStyle(Paint.Style.FILL);//充满
            paint.setColor(Color.WHITE);
            paint.setAntiAlias(true);// 设置画笔的锯齿效果
            //矩形边框的的画笔
            whit = new Paint(); //设置一个笔刷大小是3的黄色的画笔
            whit.setStyle(Paint.Style.STROKE);
            whit.setColor(mTopBarColor);
            whit.setStrokeWidth(1);
            whit.setAntiAlias(true);// 设置画笔的锯齿效果
            //设置字体
            textPaint = new Paint();
            textPaint.setColor(mTopBarColor);
            textPaint.setTextSize(DisplayMetricsTool.canvasTextSize(mContext));
            textPaint.setStyle(Paint.Style.FILL);
            //该方法即为设置基线上那个点究竟是left,center,还是right  这里我设置为center
            textPaint.setTextAlign(Paint.Align.CENTER);
            textPaint.setAntiAlias(true);
            attr.recycle();
    }
    

    3.自定义一个方法设置进度值,默认满进度100.

       /**
         * 顶部进度条初始百分比进度
         */
        private float mOriginalPercent = 0f;
       /**
         * 设置初始进度值,默认进度最大值为100
         *
         * @param progress
         */
    
        public void setOriginalProgress(int progress) {
            if (month) {//按年显示
                progress = 100 - (year - progress);//总进度减去今年和传入年份的差值
            } else {
                //按月显示
                progress = (int) (progress * 8.4);//12月占100的百分比8.4
            }
            mOriginalPercent = progress / 100f;
            if (mOriginalPercent < 0) {
                mOriginalPercent = 0.0f;
            } else if (mOriginalPercent > 1.0f) {
                mOriginalPercent = 1.0f;
            }
        }
    
    

    4.需要在onMeasure方法中计算,当前传入的进度换算之后占在屏幕的哪个位置。

        //获得密度转像素的大小
        private int dip_size;
        // view的宽
        private int mViewWidth;
       // view的高
       private int mViewHeight;
       // 底部进度条的宽
      private int mBottomBarWidth;
    
       //底部进度条左边位置
     private int mBottomBarLeft;
        // 底部进度条上边位置
     private int mBottomBarTop;
      // 底部进度条右边位置
     private int mBottomBarRight;
      //  底部进度条底边位置
    private int mBottomBarBottom;
    //  顶部进度条的右边位置
      private int mTopBarRight;
       @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    
            int widthSize = MeasureSpec.getSize(widthMeasureSpec);
            int widthMode = MeasureSpec.getMode(widthMeasureSpec);
            int heightSize = MeasureSpec.getSize(heightMeasureSpec);
            int heightMode = MeasureSpec.getMode(heightMeasureSpec);
    
            if (widthMode == MeasureSpec.AT_MOST) {
                mViewWidth = mTvWidth * 4;
            } else if (widthMode == MeasureSpec.EXACTLY) {
                if (mTvWidth * 4 >= widthSize) {
                    mViewWidth = mTvWidth * 4;
                } else {
                    mViewWidth = widthSize;
                }
            }
    
            widthMeasureSpec = MeasureSpec.makeMeasureSpec(mViewWidth, MeasureSpec.EXACTLY);
    
            if (heightMode == MeasureSpec.AT_MOST) {
                mViewHeight = mTvHeight + 4 * mPadding;
            } else if (heightMode == MeasureSpec.EXACTLY) {
                if (heightSize <= mTvHeight + 4 * mPadding) {
                    mViewHeight = mTvHeight + 4 * mPadding;
                } else {
                    mViewHeight = heightSize;
                }
            }
    
            heightMeasureSpec = MeasureSpec.makeMeasureSpec(mViewHeight, MeasureSpec.EXACTLY);
    
            mBottomBarWidth = mViewWidth - 2 * mTvWidth - 6 * mPadding;
    
            mBottomBarLeft = mTvWidth + 3 * mPadding;
            mBottomBarRight = mViewWidth - mTvWidth - 3 * mPadding;
            mBottomBarBottom = (mViewHeight - mTvHeight) / 2 + mTvHeight;
            mBottomBarTop = mBottomBarBottom - mBarHeight;
    
            mCircleY = mBottomBarBottom - mBarHeight / 2;//圆心的坐标
    
            mPosition = (int) Math.round((mBottomBarWidth - DisplayMetricsTool.getWidth(mContext) / 6) * mOriginalPercent + 0.5) + mBottomBarLeft;
            if (mPosition <= mBottomBarLeft) {
                mPosition = mBottomBarLeft;
            } else if (mPosition >= mBottomBarRight) {
                mPosition = mBottomBarRight;
            }
    
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        }
    

    5.因为需要左右滑动,所以我们采用GestureDetector的手势滑动管理,判断当用户点击在椭圆中才能让滑动改变

      GestureDetector   mGestureDetector = new GestureDetector(mContext, new GestureDetector.OnGestureListener() {
    
                @Override
                public boolean onSingleTapUp(MotionEvent e) {
                    if (mWhereClickedState == WHERR_CLICKED_CIRCLE) {
                    } else if (mWhereClickedState == WHERR_CLICKED_BAR) {
                        mPosition = (int) Math.round(e.getX() + 0.5);
                        if (mPosition >= mBottomBarRight) {
                            mPosition = mBottomBarRight;
                        } else if (mPosition <= mBottomBarLeft) {
                            mPosition = mBottomBarLeft;
                        }
                        MySeekBar.this.invalidate();
                    }
                    return false;
                }
    
                @Override
                public void onShowPress(MotionEvent e) {
                    // TODO Auto-generated method stub
    
                }
    
                @Override
                public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX,
                                        float distanceY) {
                  /*  if (mWhereClickedState == WHERR_CLICKED_CIRCLE) {*/
                    mPosition = (int) Math.round(e2.getX() + 0.5);
                    if (mPosition >= mBottomBarRight) {
                        mPosition = mBottomBarRight;
                    } else if (mPosition <= mBottomBarLeft) {
                        mPosition = mBottomBarLeft;
                    }
                    MySeekBar.this.invalidate();
                  /*  } else if (mWhereClickedState == WHERR_CLICKED_BAR) {
                    }*/
                    return false;
                }
    
                @Override
                public void onLongPress(MotionEvent e) {
                    // TODO Auto-generated method stub
    
                }
    
                @Override
                public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
                                       float velocityY) {
                    // TODO Auto-generated method stub
                    return false;
                }
    
                @Override
                public boolean onDown(MotionEvent e) {
                    float event_x = e.getX();
                    float event_y = e.getY();
                    mWhereClickedState = judgeWhereClicked(event_x, event_y);
                    if (mWhereClickedState == WHERR_CLICKED_VIEW) {
                        return false;
                    } else {
                        return true;
                    }
                }
            });
        /**
         * 判断用户点击位置状态
         *
         * @param x 用户点击的x坐标
         * @param y 用户点击的y坐标
         * @return 返回用户点击未知状态:
         * WHERR_CLICKED_CIRCLE
         * WHERR_CLICKED_BAR
         * WHERR_CLICKED_VIEW
         */
        public int judgeWhereClicked(float x, float y) {
            if (rectF.contains(x, y)) {
                return WHERR_CLICKED_CIRCLE;  //s_x和s_y界定的区域,即圆圈区域
            } else {
                return WHERR_CLICKED_VIEW;  //view除了上述两部分的部分
            }
        }
    

    6.现在画东西的笔有了,架子有了,装饰材料有了,只剩往一块白布里面画东西。就成了。 接下来最重要的就是在onDraw方法中,把UI画上去。

        @Override
        protected void onDraw(Canvas canvas) {
    
    
            mTopBarRight = mPosition;
            mCircleX = mTopBarRight;
    
            mPaint.setColor(mBottomBarColor);
            canvas.drawRect(mBottomBarLeft, mBottomBarTop, mBottomBarRight, mBottomBarBottom, mPaint);
    
            mPaint.setColor(mTopBarColor);
            canvas.drawRect(mBottomBarLeft, mBottomBarTop, mTopBarRight, mBottomBarBottom, mPaint);
            if (mBottomBarRight - mTopBarRight > windows_width / 6 - 10) {//用来防止百分比是100的时候椭圆形画出边界的问题。
    
                aaa = mTopBarRight;
                rectF = new RectF(mTopBarRight, dip_size / 2 - dip_size / 5, mTopBarRight + windows_width / 6, dip_size / 2 + dip_size / 5);
    
                canvas.drawRoundRect(rectF,
    
                        (windows_width / 6) / 3, //x轴的半径
    
                        (windows_width / 6) / 3 - 5, //y轴的半径
    
                        paint);//用来画矩形白色背景
                canvas.drawRoundRect(rectF,
                        (windows_width / 6) / 3, //x轴的半径
    
                        (windows_width / 6) / 3 - 5, //y轴的半径
    
                        whit);//用来画矩形边框
            } else {
                rectF = new RectF(aaa, dip_size / 2 - dip_size / 5, aaa + windows_width / 6, dip_size / 2 + dip_size / 5);
                canvas.drawRoundRect(rectF,
    
                        (windows_width / 6) / 3, //x轴的半径
    
                        (windows_width / 6) / 3 - 5, //y轴的半径
    
                        paint);//用来画矩形白色背景
                canvas.drawRoundRect(rectF,
                        (windows_width / 6) / 3, //x轴的半径
    
                        (windows_width / 6) / 3 - 5, //y轴的半径
    
    
                        whit);//用来画矩形边框
            }
            Paint.FontMetrics fontMetrics = textPaint.getFontMetrics();
            float top = fontMetrics.top;//为基线到字体上边框的距离,即上图中的top
            float bottom = fontMetrics.bottom;//为基线到字体下边框的距离,即上图中的bottom
    
            int baseLineY = (int) (rectF.centerY() - top / 2 - bottom / 2);//基线中间点的y轴计算公式
            canvas.drawText(getProgress() + myText, rectF.centerX(), baseLineY, textPaint);
        }
    

    7.获得当前进度由于这个项目是按年或者月显示的,所以获取进度时和设置进度时都需要按照满进度值100来进行换算,设置获得进度的方法getProgress()。

    /**
         * 获得当前进度值
         *
         * @return int
         */
        public int getProgress() {
    
            float percent = (mPosition - mBottomBarLeft) * 1.0f / (mBottomBarWidth -windows_width/ 6);
            if (percent < 0.0) {
                percent = 0f;
            } else if (percent > 1) {
                percent = 1f;
            }
    
            int progress = ((int) Math.round(percent * 100 + 0.5) - 1);
            if (progress <= 0) {
                progress = 1;
            } else if (progress > 100) {
                progress = 100;
            }
            if (month) {
                return year - (100 - progress);
            } else {
                return progress / 8 == 0 ? 1 : progress / 8;
            }
    
        }
    

    8.项目需要按左右按钮的时候必要你动态设置进度值,并改变椭圆的位置,那么我们只需要改变进度值,然后不断去onDraw重新绘制界面就行了。代码如下

       /**
         * 通过传入进度值来更新进度条
         */
        public void updatePositionFromProgress(int progress) {
            if (month) {//按年显示
                progress = 100 - (year - progress);//总进度减去今年和传入年份的差值
            } else {
                //按月显示
                progress = (int) (progress * 8.4);
            }
    
            float percent = progress / 100f;
            if (percent < 0) {
                percent = 0.0f;
            } else if (percent > 1.0f) {
                percent = 1.0f;
            }
            mPosition = (int) Math.round((mBottomBarWidth -windows_width / 6)* percent + 0.5) + mBottomBarLeft;
            if (mPosition <= mBottomBarLeft) {
                mPosition = mBottomBarLeft;
            } else if (mPosition >= mBottomBarRight) {
                mPosition = mBottomBarRight;
            }
            this.invalidate();
        }
    

    #######此时基本的实现已经完成。使用方式基本就是在XML设置颜色,字体之类的事,然后动态去设置进度--->代码如下

                
                 year = calendar.get(Calendar.YEAR);//当前的年
                    seekbar = (MySeekBar) view.findViewById(R.id.seekbar);
                    seekbar.setOriginalProgress(year);
    
    至此整个功能差不多已经完成。刚开始的时候也是研究了网上有关的项目去进行研究,然后自己重新更改封装,弄成符合自己项目需要东西,希望能给有需要的同学一点思路,不要想轮子能完全给你解决项目的难点,而是提供给你一个解决问题的思路,google,github,爆栈等等,有难点的时候大家可以好好利用。

  • 下一篇准备写自定义recyclerview的上拉加载功能,因为看了网上大多数的开源项目,基本上都是华丽但不实用,功能太多真正能在项目用得上的很少,增加了方法树,包的大小,所以将会写一个只有上拉加载的recyclerview功能
  • 你可能感兴趣的:(Android自定义SeekBar(带数字显示跟着变化))