Android 自定義 View 之 實現一個多功能的 IndicatorView

依然範特稀西發表於2017-05-23

Indicator (指示器) 可能大家都見的比較多了,在一個APP中,有很多場景都會用到Indicator,比如第一次安裝APP 時的引導頁,首頁上面的廣告Banner ,又或者是Tab下面的Indicator。Indicator 一般配合ViewPager 使用,它能很直觀的反應出ViewPager 中一共有多少頁,當前選中的是哪一頁,使用者能夠很容易的看出是否還可以滑動,使用者體驗較好。那麼本篇文章就講一下如何通過自定義View來實現一個漂亮簡潔的IndicatorView。

Indicator的展示形式

我們使用Indicator時,常見的有三種形式,第一種是根據ViewPager 的總頁數展示一排小圓點,選中和未選中分別標為不同的顏色。這也是普遍的使用Indicator的效果,效果如下:

Android 自定義 View 之 實現一個多功能的 IndicatorView
image.png

第二種形式是根據ViewPager 的總頁數展示一排圓點,並且用數字標出其順序(1,2,3,4 ...),大概效果如下:

Android 自定義 View 之 實現一個多功能的 IndicatorView
image.png

第三種形式和第二種形式差不多,同樣根據ViewPager總頁數展示一片圓點,但是用字母標出順序(A,B,C,D ....), 比如 :魅族手機螢幕的切換的Indicator。效果如下:

Android 自定義 View 之 實現一個多功能的 IndicatorView
image.png

本篇文章就通過自定義View來實現這三種IndicatorView。

通過自定義View 實現多功能的CircleIndicatorView

首先整理一下思路,CircleIndicatorView ,它是由一組圓 組成的,只不過圓有多種狀態,選中、未選中、顯示字母、顯示數字等等。看著有這麼多中狀態,感覺挺複雜,但是其實不復雜,只是用不同的變數來控制不同的狀態即可。其他的先不考慮,我們只要先把這一排圓畫出來,剩下的就比較容易了。要確定一個圓的,需要知道圓的半徑和圓心,半徑是我們可以配置的,那麼,其實核心就是計算這一排的圓點的圓心位置。圓心 y 的位置是固定的(View 高的一般),因此我們只需要計算每個圓的圓心x的位置,看下圖,其實很容易就能找到規律,規律如下: 第一個圓的x 就等於圓的半徑,從第二個圓開始,當前圓的圓心x 座標為 上一個圓的x 座標 + (radius * 2 + mSpace)。 其中mSpace 是圓之間的間距。

Android 自定義 View 之 實現一個多功能的 IndicatorView
圓心x座標計算公式.png

有了上面的規律,我們只需要一個迴圈就能找出所有圓的圓心位置。程式碼如下:

/**
     * 測量每個圓點的位置
     */
    private void measureIndicator(){
        mIndicators.clear();
        float cx = 0;
        for(int i=0;i<mCount;i++){
            Indicator indicator = new Indicator();
            if( i== 0){
                cx = mRadius + mStrokeWidth;
            }else{
                cx += (mRadius + mStrokeWidth) * 2 +mSpace;
            }

            indicator.cx = cx;
            indicator.cy = getMeasuredHeight() / 2;

            mIndicators.add(indicator);
        }
    }複製程式碼

我們用Indicator類記錄了每個圓的圓心位置,並且儲存在一個列表裡面,現在有了所有圓的資料,我們就可以繪製 了。

  @Override
    protected void onDraw(Canvas canvas) {

        for(int i=0;i<mIndicators.size();i++){

            Indicator indicator = mIndicators.get(i);
            float x = indicator.cx;

            float y = indicator.cy;

            if(mSelectPosition == i){
                mCirclePaint.setStyle(Paint.Style.FILL);
                mCirclePaint.setColor(mSelectColor);
            }else{
                mCirclePaint.setColor(mDotNormalColor);
                if(mFillMode != FillMode.NONE){
                    mCirclePaint.setStyle(Paint.Style.STROKE);
                }else{
                    mCirclePaint.setStyle(Paint.Style.FILL);

                }
            }
            canvas.drawCircle(x,y, mRadius, mCirclePaint);

            // 繪製小圓點中的內容
            if(mFillMode != FillMode.NONE){
                String text = "";
                if(mFillMode == FillMode.LETTER){
                    if(i >= 0 && i<LETTER.length){
                        text = LETTER[i];
                    }
                }else{
                    text = String.valueOf(i+1);
                }
                Rect bound = new Rect();
                mTextPaint.getTextBounds(text,0,text.length(),bound);
                int textWidth = bound.width();
                int textHeight = bound.height();

                float textStartX = x - textWidth / 2;
                float textStartY = y + textHeight / 2;
                canvas.drawText(text,textStartX,textStartY, mTextPaint);
            }

        }

    }複製程式碼

繪製的程式碼很簡單,無非就是迴圈我們儲存的列表,拿出每一個Indicator,然後繪製圓就行了,除此之外就是一些狀態的判斷,比如,是否繪製數字、字母和選中狀態等等 。

到此,我們的CircleIndicatorView 的繪製工作算是完成了,並且也能夠顯示在介面上了,但是它現在還是一個單獨的View。我們前面說過,IndicatorView 一般是配合ViewPager使用的,它是隨著Viewpager的切換而改變的,因此我們需要將IndicatorView 與ViewPager 進行關聯。

要讓CircleIndicatorView 與ViewPager 關聯,首先需要 CircleIndicatorView 實現 ViewPager.OnPageChangeListener 介面。然後在onPageSelected方法中記錄當前頁的位置。程式碼如下:

 @Override
    public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {

    }

    @Override
    public void onPageSelected(int position) {
        mSelectPosition = position;
        invalidate();
    }

    @Override
    public void onPageScrollStateChanged(int state) {

    }複製程式碼

第二步,向外提供一個API ,設定CircleIndicatorView 與 ViewPager 關聯。

  /**
     *  與ViewPager 關聯
     * @param viewPager
     */
    public void setUpWithViewPager(ViewPager viewPager){
        releaseViewPager();
        if(viewPager == null){
            return;
        }
        mViewPager = viewPager;
        mViewPager.addOnPageChangeListener(this);
        int count = mViewPager.getAdapter().getCount();
        setCount(count);
    }複製程式碼

通過如上兩步,就建立了CircleIndicatorView 與ViewPager的關聯,只需要呼叫方法setUpWithViewPager 就OK。

到此就完了嗎?當然還沒有完,其實還有一個小細節,那就是IndicatorView 因該是可以點選的,點選Indicator小圓點就能切換ViewPager 到對應的頁面(如 Iphone 和 魅族手機的 螢幕切換,點選indicator小圓點就可以切換 ,以前沒有注意的現在可以去試一下 ),那麼我們也來實現這樣一個功能,其實很簡單,重寫onTouchEvent方法 ,監聽ACTION_DOWN 事件,然後獲取點選螢幕的座標,與所繪製的圓位置比較,如果點選區域在圓的範圍內,就點選了該Indicator。點選之後,切換Viewpager到對應頁面。程式碼如下:

  @Override
    public boolean onTouchEvent(MotionEvent event) {
        float xPoint = 0;
        float yPoint = 0;
        switch (event.getAction()){
            case MotionEvent.ACTION_DOWN:
                xPoint = event.getX();
                yPoint = event.getY();
                handleActionDown(xPoint,yPoint);
                break;

        }

        return super.onTouchEvent(event);
    }

    private void handleActionDown(float xDis,float yDis){
        for(int i=0;i<mIndicators.size();i++){
            Indicator indicator = mIndicators.get(i);
            if(xDis < (indicator.cx + mRadius+mStrokeWidth)
                    && xDis >=(indicator.cx - (mRadius + mStrokeWidth))
                    && yDis >= (yDis - (indicator.cy+mStrokeWidth))
                    && yDis <(indicator.cy+mRadius+mStrokeWidth)){
                 // 找到了點選的Indicator
                // 切換ViewPager
                 mViewPager.setCurrentItem(i,false);
                 // 回撥
                if(mOnIndicatorClickListener!=null){
                    mOnIndicatorClickListener.onSelected(i);
                }
                break;
            }
        }
    }複製程式碼

到此,我們自定義IndicatorView 的工作就差不多完成了,但是現在的IndicatorView 還不是很靈活,我們要讓它的可配置性更強,就應該提供更多的API 來讓IndicatorView 使用更加靈活方便,因此,最後一步,加上一些自定義屬性來提高它的靈活性。自定義瞭如下一些屬性:

屬性名 屬性意義 取值
indicatorRadius 設定指示器圓點的半徑 單位為 dp 的值
indicatorBorderWidth 設定指示器的border 單位為 dp 的值
indicatorSpace 設定指示器之間的距離 單位為 dp 的值
indicatorTextColor 設定指示器中間的文字顏色 顏色值,如:#FFFFFF
indicatorColor 設定指示器圓點的顏色 顏色值
indicatorSelectColor 設定指示器選中的顏色 顏色值
fill_mode 設定指示器的模式 列舉值:有三種,分別是letter,number和none

自定義屬性檔案如下:

 <declare-styleable name="CircleIndicatorView">
        <attr name="indicatorRadius" format="dimension"/>
        <attr name="indicatorBorderWidth" format="dimension"/>
        <attr name="indicatorSpace" format="dimension"/>
        <attr name="indicatorTextColor" format="color"/>
        <attr name="indicatorColor" format="color"/>
        <attr name="indicatorSelectColor" format="color"/>
        <attr name="fill_mode">
            <enum name="letter" value="0"/>
            <enum name="number" value="1"/>
            <enum name="none" value="2"/>
        </attr>
    </declare-styleable>複製程式碼

通過上面這些屬性,我們就可以很好的定製IndicaotorView 了,比如,自定義圓的大小,顏色,border,文字的顏色,選中的顏色和展示的模式等等。

最終的效果如下:

Android 自定義 View 之 實現一個多功能的 IndicatorView
CircleIndicator最終效果.gif

CircleIndicatorView 使用方法

上面講了自定義IndicatorView 的思路、過程 和一些關鍵程式碼,為了使用方便,已經將CircleIndicatorView封裝成了一個庫,以下介紹一下CircleIndicatorView API的使用。Github 地址:github.com/pinguo-zhou…

Dependency

1, 最外層build.gradle新增如下程式碼:

   allprojects {
       repositories {
    ...
    maven { url 'https://jitpack.io' }
   }
}複製程式碼

2, app 層buid.gradle dependencies 中 新增如下程式碼:

compile 'com.github.pinguo-zhouwei:CircleIndicatorView:v1.0.0'複製程式碼

使用方法如下:
1, xml 新增CircleIndicatorView,配置相關屬性

   <com.zhouwei.indicatorview.CircleIndicatorView
           android:id="@+id/indicator_view"
           android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:layout_alignParentBottom="true"
           android:layout_marginBottom="50dp"
           android:layout_centerHorizontal="true"
           app:indicatorSelectColor="#00A882"
           app:fill_mode="letter"
           app:indicatorBorderWidth="2dp"
           app:indicatorRadius="8dp"
           app:indicatorColor="@color/colorAccent"
           app:indicatorTextColor="@android:color/white"
           />複製程式碼

2, 在程式碼中與ViewPager 關聯

         // 初始化ViewPager 相關
          mViewPager = (ViewPager) findViewById(R.id.viewpager);
          mPagerAdapter = new ViewPagerAdapter();
          mViewPager.setAdapter(mPagerAdapter);

          mIndicatorView = (CircleIndicatorView) findViewById(R.id.indicator_view);
          // 關聯ViewPager 
          mIndicatorView.setUpWithViewPager(mViewPager);複製程式碼

上面所有列舉的屬性,出了在xml 中配置外,也可以在程式碼中設定,如下:

          // 在程式碼中設定相關屬性

          CircleIndicatorView indicatorView = (CircleIndicatorView) findViewById(R.id.indicator_view3);
          // 設定半徑
          indicatorView.setRadius(DisplayUtils.dpToPx(15));
          // 設定Border
          indicatorView.setBorderWidth(DisplayUtils.dpToPx(2));

          // 設定文字顏色
          indicatorView.setTextColor(Color.WHITE);
          // 設定選中顏色
          indicatorView.setSelectColor(Color.parseColor("#FEBB50"));
          //
          indicatorView.setDotNormalColor(Color.parseColor("#E38A7C"));
          // 設定指示器間距
          indicatorView.setSpace(DisplayUtils.dpToPx(10));
          // 設定模式
          indicatorView.setFillMode(CircleIndicatorView.FillMode.LETTER);

          // 最重要的一步:關聯ViewPager
          indicatorView.setUpWithViewPager(mViewPager);複製程式碼

最後

以上就是通過自定義View的方式實現一個多功能的IndicatorView的全部內容,內容很簡單,通過自定義的View方式實現很方便,免得每次還得找設計師要切圖啊(能自己動手的,就不找設計師要了。?)。如有問題歡迎交流,最後,如果你覺得不錯的話,歡迎github 賞個start啊!!輪子傳送門 github.com/pinguo-zhou…

相關文章