ViewPager自定义指示器

效果图如下:

这里写图片描述

1.先在attrs文件中定义指示器的四个属性

    <attr name="indicatorIcon" format="reference"/>
    <attr name="indicatorMargin" format="dimension"/>
    <attr name="indicatorSmooth" format="boolean"/>
    <attr name="indicatorSize" format="dimension"/>

    <declare-styleable name="IndicatorView">
        <!--指示器样式-->
        <attr name="indicatorIcon" />
        <!--指示器的间距-->
        <attr name="indicatorMargin" />
        <!--指示器是否显示滑动效果-->
        <attr name="indicatorSmooth" />
        <!--指示器的大小-->
        <attr name="indicatorSize" />
    </declare-styleable>

2.在自定义view的构造方法中获取自定义样式的值

private void init(Context context, AttributeSet attrs, int defStyleAttr) {

        //设置指示器大小的默认值
        mIndicatorSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,16,getResources().getDisplayMetrics());
        //设置指示器间距的默认值
        mDefaultMargin = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,5,getResources().getDisplayMetrics());

        //通过TypedArray获取属性
        TypedArray typedArray = context.obtainStyledAttributes(attrs,R.styleable.IndicatorView,defStyleAttr,0);

        mIndicator = typedArray.getDrawable(R.styleable.IndicatorView_indicatorIcon);
        mMargin = (int) typedArray.getDimension(R.styleable.IndicatorView_indicatorMargin,mDefaultMargin);
        mSmooth = typedArray.getBoolean(R.styleable.IndicatorView_indicatorSmooth,true);
        mIndicatorSize = (int) typedArray.getDimension(R.styleable.IndicatorView_indicatorSize,mIndicatorSize);

        //使用完之后记得释放资源
        typedArray.recycle();

        //通过获取的指示器大小设置指示器绘制的边界
        mIndicator.setBounds(0,0,mIndicatorSize,mIndicatorSize);
    }

3.重写onMeasure方法设置整个控件的宽高

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
       setMeasuredDimension(measureWidth(widthMeasureSpec),measureHeight(heightMeasureSpec));
    }

    /**
     * 测量高
     * @param widthMeasureSpec
     * @return
     */
    private int measureWidth(int widthMeasureSpec) {
        final int mode = MeasureSpec.getMode(widthMeasureSpec);
        final int size = MeasureSpec.getSize(widthMeasureSpec);

        int width;
        //计算整个控件的宽度,其中 mCount 是获取到的ViewPager的页数
        int desired = getPaddingLeft() + mIndicatorSize * mCount + mMargin * (mCount - 1) + getPaddingRight();
        mContextWidth = desired;

        if (mode == MeasureSpec.EXACTLY) {
            //如果是match_parent,则选取屏幕宽度和计算得到的宽度中较大的那个作为控件的宽度
            width = Math.max(desired,size);
        }else {
            if (mode == MeasureSpec.AT_MOST) {
                //如果是wrap_parent,则选取屏幕宽度和计算得到的宽度中较小的那个作为控件的宽度
                width = Math.min(desired,size);
            }else {
                //还有一种情况是 UNSPECIFIED ,一般是系统内部测量的过程中使用,这种情况也可以忽略掉
                width = desired;
            }
        }

        mWidth = width;
        return width;
    }

    /**
     * 测量高,过程与测量宽类似
     * @param heightMeasureSpec
     * @return
     */
    private int measureHeight(int heightMeasureSpec) {
        final int mode = MeasureSpec.getMode(heightMeasureSpec);
        final int size = MeasureSpec.getSize(heightMeasureSpec);

        int height;
        int desired = getPaddingTop() + mIndicatorSize + getPaddingBottom();

        if (mode == MeasureSpec.EXACTLY) {
            height = size;
        }else {
            if (mode == MeasureSpec.AT_MOST) {
                height = Math.min(desired,size);
            }else {
                height = desired;
            }
        }

        return height;
    }

3.重写onDraw方法绘制控件

    @Override
    protected void onDraw(Canvas canvas) {

        canvas.save();
        /**
         * 这里是计算绘制控件的x轴上的起点位置
         * 当wrap_content并且控件宽度小于屏幕宽度时,计算得到的宽度和实际宽度相同,mWidth/2 - mContextWidth/2 = 0,这种情况很好理解
         * 再看当match_content的情况,控件的计算宽度小于屏幕宽度,则mWidth > mContextWidth,则计算得到的left是计算得到的控件距离屏幕左端的距离,
         * 控件将绘制在屏幕水平方向的中间,不清楚可以在纸上画下,其他情况也类似
         */
        int left = mWidth/2 - mContextWidth/2 + getPaddingLeft();
        canvas.translate(left,getPaddingTop());
        //依次绘制指示器
        for (int i = 0 ; i < mCount ; i++) {
            mIndicator.setState(EMPTY_STATE_SET);
            mIndicator.draw(canvas);
            canvas.translate(mIndicatorSize + mMargin,0);
        }
        canvas.restore();

        /**
         * 下面是绘制选中的指示器的样式
         */
        float leftDraw = ( mIndicatorSize + mMargin ) * ( mSelectedItem + mOffset );
        canvas.translate(left,getPaddingTop());
        canvas.translate(leftDraw,0);
        mIndicator.setState(SELECTED_STATE_SET);
        mIndicator.draw(canvas);

    }

4.设置viewpager

    public void setViewPager(ViewPager viewPager) {
        if (viewPager == null) {
            return;
        }
        PagerAdapter pagerAdapter = viewPager.getAdapter();
        if (pagerAdapter == null) {
            throw new RuntimeException("请先设置viewpager的adaper");
        }
        mCount = pagerAdapter.getCount();
        //注册viewpager的滑动事件,用于改变指示器的绘制
        viewPager.addOnPageChangeListener(this);
        mSelectedItem = viewPager.getCurrentItem();
        invalidate();
    }

5.自定义view实现viewpager的ViewPager.OnPageChangeListener接口,监听viewpager的滑动用于指示器做出相应的改变

    /**
     * 因为自定义控件注册了viewpager的滑动监听,
     * 为了外部能够同样监听到滑动事件,所以提供该接口
     * @param pageChangeListener
     */
    public void setOnPageChangeListener(ViewPager.OnPageChangeListener pageChangeListener) {
        this.mPageChangeListener = pageChangeListener;
    }

    @Override
    public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
        if (mSmooth){
            //允许显示滑动效果
            mSelectedItem = position;
            mOffset = positionOffset;
            //通知view重绘
            invalidate();
        }
        if (mPageChangeListener != null) {
            mPageChangeListener.onPageScrolled(position,positionOffset,positionOffsetPixels);
        }
    }

    @Override
    public void onPageSelected(int position) {
        //当有page被选中后通知view重绘
        mSelectedItem = position ;
        invalidate();

        if(mPageChangeListener != null){
            mPageChangeListener.onPageSelected(position);
        }
    }

    @Override
    public void onPageScrollStateChanged(int state) {
        if(mPageChangeListener != null){
            mPageChangeListener.onPageScrollStateChanged(state);
        }
    }

源码地址

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值