【原創(chuàng)】ViewPager的指示器簡單寫法(一)

一、先看效果

viewpager指示器.gif

二、分析

這個效果用動畫來寫,并不是很好實現(xiàn)??梢钥紤]用自定義view,自定義view只需要不斷的重繪view,看起來和動畫沒有區(qū)別。
自定義view的話,我們來分析view的屬性:

  1. 圓點的半徑 (知道半徑,高度也就知道了)
  2. 圓之間的padding
  3. 圓點個數(shù)
  4. 圓形view
  5. 滾動view (下面稱呼為BarView)
    大概就這么多,其他的不需要了。再來看3,4兩個view的屬性:

3.1 圓心
3.2 半徑
3.3 是否選中 (涉及到填充顏色)

BarView我們可以看成首尾兩個圓,中間一個矩形的組合體:

4.1 左邊圓的圓心和半徑
4.2 右邊圓的圓心和半徑 (半徑和左邊的圓一致)
4.3 矩形的寬度高度 (這個并不需要,已知左右圓,寬高都是可以求出來的)

全部需要的條件就這么多,接下來是代碼分析了。

三、代碼實現(xiàn)

首先建立兩個對象,一個圓點對象,一個滑塊對象:


/**
 * 圓點對象
 */
public class PointView {
    private int x;
    private int y;
    private int radius;

    private boolean isChecked;
}

/**
 * 滑塊對象
 */
public class BarView {
    private int leftX;
    private int leftY;

    private int rightX;
    private int rightY;

    private int radius;
}

可以說,這兩個對象建立起來,這個view已經(jīng)完成一半了。(這里不一定一開始想的很清楚,后面可以逐漸的完善這兩個類)

首先當然繼承自view, 初始化工作:

    public class IndicatorView extends View {
        private Context mContext;
        private Paint pointPaint;
        private int childCount;
        private Paint selectPointPaint;
        private int selectPosition;
        private int scrollPosition;  //這個是滑動的時候 要到達的position
        private float ratio;
        private int pointSpace = 80; // 球之間的空隙
        private int radius = 40; // 球半徑
        private List<PointView> pointViews;
        private BarView barView;
    
        public IndicatorView(Context context) {
            this(context, null);
        }
    
        public IndicatorView(Context context, @Nullable AttributeSet attrs) {
            super(context, attrs);
            init(context, attrs);
        }
    
        private void init(Context context, AttributeSet attrs) {
            mContext = context;
            pointPaint = new Paint();
            pointPaint.setAntiAlias(true);
            pointPaint.setColor(Color.GRAY);
    
            selectPointPaint = new Paint();
            selectPointPaint.setAntiAlias(true);
            selectPointPaint.setColor(Color.parseColor("#eb1c42"));
        }
    }

這個時候需要繪制了,但是從哪里繪制呢?我們這個view是配合viewpager使用的,所以需要暴漏一個方法,設(shè)置一個viewpager進來:

/**
     * 關(guān)聯(lián)viewpager
     * @param viewPager
     */
    public void setViewPager(ViewPager viewPager) {
        childCount = viewPager.getAdapter().getCount();
        initPoints();
        viewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
            @Override
            public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
                scrollPosition = position;
                Log.d("------->", "position:" + position + ", positionOffset:" + positionOffset + ", positionOffsetPixels:" + positionOffsetPixels);
                ratio = positionOffset;
                if(ratio >= 1 || ratio <= 0){
                    return;
                }
                compute();
                invalidate();
            }

            @Override
            public void onPageSelected(int position) {
                selectPosition = position;
                ratio = 0;
                compute();
                invalidate();
            }

            @Override
            public void onPageScrollStateChanged(int state) {

            }
        });
        ratio = 0;
        compute();
        invalidate();
    }

當viewpager設(shè)置進來的時候,我們第一步拿到頁面的個數(shù),去initPoints()初始化小圓點:

/**
     * 初始化點
     */
    private void initPoints() {
        pointViews = new ArrayList<>();
        for (int i = 0; i < childCount; i++) {
            PointView pointView = new PointView();
            pointViews.add(pointView);
        }
        //順便初始化bar
        barView = new BarView();
    }

然后監(jiān)聽viewpager的滾動,在滾動里面去計算BarView的位置和設(shè)置圓點的選中狀態(tài)。
在監(jiān)聽方法里面,需要注意 scrollPosition 和 selectPosition 的區(qū)別,這個 scrollPosition 是頁面將要到達的頁面的 index,而 selectPosition 是記錄當前選中圓點的index。兩者的區(qū)別比較大,用處也不一樣。這個 scrollPosition 我們在后面還需要用到。
監(jiān)聽方法里面有個 compute() 計算方法:


    /**
     * 繪制之前準備, 這個計算一定要在canvas外面計算
     */
    private void compute() {
        //計算球位置
        for (int i = 0; i < pointViews.size(); i++) {
            PointView pointView = pointViews.get(i);
            pointView.setRadius(radius);
            pointView.setX(radius * (2 * i + 1) + pointSpace* i);
            pointView.setY(radius);
            pointView.setChecked(selectPosition == i);
        }
        //計算bar位置
        PointView selectPointView = pointViews.get(selectPosition);
        int selectX = selectPointView.getX();
        int selectY = selectPointView.getY();
        if(selectPosition <= scrollPosition){
            //往右是增加右邊圓的圓心
            barView.setLeftX(selectX);
            barView.setLeftY(selectY);
            barView.setRightX((int) (selectX + (2 * radius + pointSpace) * ratio));
            barView.setRightY(selectY);
            barView.setRadius(radius);
        }else{
            //往左是減少左邊圓的圓心
            barView.setRightX(selectX);
            barView.setRightY(selectY);
            barView.setLeftX((int) (selectX - (2 * radius + pointSpace) * (1 - ratio)));
            barView.setLeftY(selectY);
            barView.setRadius(radius);
        }
    }
    

圓點(也就是小球)的位置很好計算,半徑圓心和選中狀態(tài),幾乎計算一遍就行。主要是BarView的位置,它是隨著viewpager滾動而改變的,viewpager滾動有向左和向右兩個
方向,這個時候 scrollPosition 就起到關(guān)鍵性的作用了,往右,這個 index 是大于當前 selectPosition 的,往左,這個 index 是小于或者等于(經(jīng)過打印是等于,嚴謹點,小于也判斷了) selectPosition 的,
那就有兩種計算方法,往右,左邊圓心等于選中圓的圓心,右邊圓心等于選中圓心加上偏移量,偏移量可以計算得出,代碼如上,往左,右邊圓心是等于選中圓的圓心,左邊圓心是選中圓心減去偏移量。
計算完調(diào)用 invalidate(),自動重繪頁面,調(diào)用 view 的 onDraw() 方法。接下來就是重點的onDraw()方法:


    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //畫圓點
        for (PointView pointView : pointViews) {
            canvas.drawCircle(pointView.getX(), pointView.getY(), pointView.getRadius(), pointView.isChecked()?selectPointPaint:pointPaint);
        }
        //畫bar的頭尾圓
        canvas.drawCircle(barView.getLeftX(), barView.getLeftY(), barView.getRadius(), selectPointPaint);
        canvas.drawCircle(barView.getRightX(), barView.getRightY(), barView.getRadius(), selectPointPaint);
        //畫bar的中間rect
        canvas.drawRect(new RectF(barView.getLeftX(), 0, barView.getRightX(), radius * 2), selectPointPaint);
    }
    

是不是很簡單?為什么重點部分這么少代碼,因為計算已經(jīng)在外面計算過了,這里只需要把數(shù)據(jù)拿來繪制一下就好了。
到這是不是結(jié)束了,不,自定義view除了 onDraw() 這個關(guān)鍵方法還有 onMeaure() 沒有用到,如果不計算view的高度,默認是充滿屏幕的,就無法動態(tài)放置 view,所以最后:

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        //設(shè)置view寬高 不設(shè)置就是默認全屏view,沒辦法改變位置
        if(pointViews != null && pointViews.size() > 0) {
            int width = pointViews.get(pointViews.size() - 1).getX() + radius;
            int height = radius * 2;
            setMeasuredDimension(width, height);
        }else{
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        }
    }

到這就結(jié)束了。當然還可以添加屬性,直接在xml文件就可以設(shè)置圓點 padding 和圓點半徑,這些就交給你去拓展了。附上 github地址

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容