Android自定義SeekBar(帶數(shù)字顯示跟著變化)

首先看一下最后實現(xiàn)的效果圖

seekbar.png


可左右滑動驾中,點擊加減按鈕動態(tài)改變數(shù)值和位置顯示玲躯。

思路:

1.根據(jù)圖上UI知道有三種狀態(tài)
<li>未滑中區(qū)域的灰色底部狀態(tài)</li>
<li>滑中的顏色改變狀態(tài)</li>
<li>橢圓的邊框顏色,和內(nèi)部字體顯示</li>

實現(xiàn):

首先我們自定義一個類MySeekBar繼承View减拭。并實現(xiàn)最重要的onDraw澜沟,onMeasure方法。
1.首先定義一個樣式峡谊,用來以后設置底部顏色,滑中顏色,字體大小既们,顏色等

資源樣式
    <declare-styleable name="MySeekBar">
        <attr name="mTopBarColor" format="color"></attr> 滑動時候橫條顏色
        <attr name="mBottomBarColor" format="color"></attr>默認的橫條顏色
        <attr name="strokeColor" format="color"></attr>橢圓邊框顏色
        <attr name="month" format="boolean"></attr>判斷是按年份顯示還是月份顯示
        <attr name="mytext" format="string"></attr>獲得textview顯示的后綴
    </declare-styleable>

2.實現(xiàn)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;
                }
            }
        });
    /**
     * 判斷用戶點擊位置狀態(tài)
     *
     * @param x 用戶點擊的x坐標
     * @param y 用戶點擊的y坐標
     * @return 返回用戶點擊未知狀態(tài):
     * 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界定的區(qū)域,即圓圈區(qū)域
        } else {
            return WHERR_CLICKED_VIEW;  //view除了上述兩部分的部分
        }
    }

6.現(xiàn)在畫東西的筆有了穗酥,架子有了护赊,裝飾材料有了,只剩往一塊白布里面畫東西砾跃。就成了骏啰。 接下來最重要的就是在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.項目需要按左右按鈕的時候必要你動態(tài)設置進度值,并改變橢圓的位置雏胃,那么我們只需要改變進度值请毛,然后不斷去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();
    }

#######此時基本的實現(xiàn)已經(jīng)完成瞭亮。使用方式基本就是在XML設置顏色方仿,字體之類的事,然后動態(tài)去設置進度--->代碼如下

            <ui.view.MySeekBar
                android:id="@+id/seekbar"
                android:layout_width="match_parent"
                android:layout_height="75dp"
                android:layout_centerVertical="true"
                android:layout_toLeftOf="@+id/right_ll"
                android:layout_toRightOf="@+id/left_ll"
                app:mTopBarColor="@color/color_EB7046"
                app:month="true"
                app:mytext="年" />
             year = calendar.get(Calendar.YEAR);//當前的年
                seekbar = (MySeekBar) view.findViewById(R.id.seekbar);
                seekbar.setOriginalProgress(year);
至此整個功能差不多已經(jīng)完成统翩。剛開始的時候也是研究了網(wǎng)上有關的項目去進行研究仙蚜,然后自己重新更改封裝,弄成符合自己項目需要東西厂汗,希望能給有需要的同學一點思路委粉,不要想輪子能完全給你解決項目的難點,而是提供給你一個解決問題的思路,google娶桦,github贾节,爆棧等等汁汗,有難點的時候大家可以好好利用。

<li>下一篇準備寫自定義recyclerview的上拉加載功能栗涂,因為看了網(wǎng)上大多數(shù)的開源項目知牌,基本上都是華麗但不實用,功能太多真正能在項目用得上的很少斤程,增加了方法樹角寸,包的大小,所以將會寫一個只有上拉加載的recyclerview功能<li>

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末忿墅,一起剝皮案震驚了整個濱河市扁藕,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌疚脐,老刑警劉巖亿柑,帶你破解...
    沈念sama閱讀 211,194評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異亮曹,居然都是意外死亡橄杨,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,058評論 2 385
  • 文/潘曉璐 我一進店門照卦,熙熙樓的掌柜王于貴愁眉苦臉地迎上來式矫,“玉大人,你說我怎么就攤上這事役耕〔勺” “怎么了?”我有些...
    開封第一講書人閱讀 156,780評論 0 346
  • 文/不壞的土叔 我叫張陵瞬痘,是天一觀的道長故慈。 經(jīng)常有香客問我,道長框全,這世上最難降的妖魔是什么察绷? 我笑而不...
    開封第一講書人閱讀 56,388評論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮津辩,結果婚禮上拆撼,老公的妹妹穿的比我還像新娘。我一直安慰自己喘沿,他們只是感情好闸度,可當我...
    茶點故事閱讀 65,430評論 5 384
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著蚜印,像睡著了一般莺禁。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上窄赋,一...
    開封第一講書人閱讀 49,764評論 1 290
  • 那天哟冬,我揣著相機與錄音楼熄,去河邊找鬼。 笑死浩峡,一個胖子當著我的面吹牛孝赫,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播红符,決...
    沈念sama閱讀 38,907評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼伐债!你這毒婦竟也來了预侯?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 37,679評論 0 266
  • 序言:老撾萬榮一對情侶失蹤峰锁,失蹤者是張志新(化名)和其女友劉穎萎馅,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體虹蒋,經(jīng)...
    沈念sama閱讀 44,122評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡糜芳,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,459評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了魄衅。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片峭竣。...
    茶點故事閱讀 38,605評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖晃虫,靈堂內(nèi)的尸體忽然破棺而出皆撩,到底是詐尸還是另有隱情,我是刑警寧澤哲银,帶...
    沈念sama閱讀 34,270評論 4 329
  • 正文 年R本政府宣布扛吞,位于F島的核電站,受9級特大地震影響荆责,放射性物質發(fā)生泄漏滥比。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,867評論 3 312
  • 文/蒙蒙 一做院、第九天 我趴在偏房一處隱蔽的房頂上張望盲泛。 院中可真熱鬧,春花似錦山憨、人聲如沸查乒。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,734評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽玛迄。三九已至,卻和暖如春棚亩,著一層夾襖步出監(jiān)牢的瞬間蓖议,已是汗流浹背虏杰。 一陣腳步聲響...
    開封第一講書人閱讀 31,961評論 1 265
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留勒虾,地道東北人纺阔。 一個月前我還...
    沈念sama閱讀 46,297評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像修然,于是被迫代替她去往敵國和親笛钝。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 43,472評論 2 348

推薦閱讀更多精彩內(nèi)容