美文网首页
仿写朴朴app(二)

仿写朴朴app(二)

作者: 怀念小兔 | 来源:发表于2019-03-25 16:51 被阅读0次

    今天来写这个小控件:


    不起眼的小控件

    可能有同学要说了,这不就是ViewPagerIndicator么,去github上找xxxx。
    嗯,这确实也是个解决方案,但是开头我说了,尽量不用第三方UI库,而且就朴朴里面的这个Indicator,功能很简单,也基本不存在什么太大的扩展性需求,自己写能节约一些性能。很多时候,我们只需要第三方库的一个功能,但是引用的库却包含很多我们用不到的功能,这就凭空增加了安装包的体积。
    哪怕在kotlin工程里,自定义控件我一般也用java写,以便复用到java工程,这个也不例外。
    操作朴朴app我们发现,这个indicator没有复杂的效果,只是在放图片的viewpager翻页的时候指示器改变状态,所以没必要继承ViewGroup,我们直接继承view,自己去画里面的内容。
    首先在styleable里定义几个属性:

        <declare-styleable name="SimpleIndicator">
            <!-- 指示器每个子项的宽度 -->
            <attr name="indicatorItemWidth" format="dimension|reference"/>
            <!-- 指示器每个子项间的间隔 -->
            <attr name="indicatorItemPadding" format="dimension|reference"/>
            <!-- 指示器每个子项的圆角半径 -->
            <attr name="indicatorItemRadius" format="dimension|reference"/>
            <!-- 指示器选中项的颜色 -->
            <attr name="indicatorItemSelectedColor" format="color|reference"/>
            <!-- 指示器未选中项的颜色 -->
            <attr name="indicatorItemNormalColor" format="color|reference"/>
        </declare-styleable>
    

    在这里,子项的高度直接取外层高度就行了,因为这个控件并不需要padding。

    为了让这个控件宽度能够使用wrap_content指定,必须重写它的onMeasure方法,根据我们自定义的子项宽度和间距来计算总宽度,MeasureSpec的用法网上一搜一大片,不再赘述:

        @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
            int widthMode = MeasureSpec.getMode(widthMeasureSpec);
            int widthSize = MeasureSpec.getSize(widthMeasureSpec);
            int heightMode = MeasureSpec.getMode(heightMeasureSpec);
            int heightSize = MeasureSpec.getSize(heightMeasureSpec);
            //宽度使用wrap_content的时候
            if(widthMode == MeasureSpec.AT_MOST){
                float width = getPaddingLeft() + getPaddingRight() + itemWidth * size + (size - 1) * itemPadding;
                if(width < widthSize)
                    widthSize = (int)width;
                else
                    throw new IllegalStateException("您设置的子项目宽度和间距超过了可用宽度");
            }
            //高度必须指定为具体值
            if(heightMode != MeasureSpec.EXACTLY){
                throw new IllegalStateException("indicator的高度必须指定为具体数值");
            }
            setMeasuredDimension(widthSize, heightSize);
        }
    

    然后是子项绘制位置的初始化:

        private void initItemRectangles(){
            itemRectangles = new RectF[size];
            int height = getMeasuredHeight();
            float itemDist = itemWidth + itemPadding;
            for(int i = 0; i < size; i++){
                float left = i * itemDist;
                float right = left + itemWidth;
                itemRectangles[i] = new RectF(left, 0, right, height);
            }
        }
    

    这个初始化应该在onDraw里调用并且只调用一次以避免多余的计算,大家可能注意到了drawXXXX这类方法也有不用RectF的重载,直接以上下左右坐标为入参,但是如果你那样用,每次onDraw的时候都会重新计算一次,作为硬件的老板,我们要学会为这些可爱的员工减负啊。
    上面的size哪来的?当然是跟viewPager关联的时候取到的了:

        public void setupWithViewPager(ViewPager viewPager){
            PagerAdapter adapter = viewPager.getAdapter();
            if(adapter == null)
                throw new IllegalStateException("this viewPager has not set adapter yet");
            this.size = adapter.getCount();
            viewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
                @Override
                public void onPageScrolled(int i, float v, int i1) {
    
                }
    
                @Override
                public void onPageSelected(int i) {
                    selectedIndex = i;
                    invalidate();
                }
    
                @Override
                public void onPageScrollStateChanged(int i) {
    
                }
            });
        }
    

    最后是onDraw方法:

        @Override
        protected void onDraw(Canvas canvas) {
            super.onDraw(canvas);
            if(itemRectangles == null)
                initItemRectangles();
            for(int i = 0; i < itemRectangles.length; i++){
                drawItem(canvas, itemRectangles[i], i == selectedIndex);
            }
        }
        protected void drawItem(Canvas canvas, RectF rect, boolean selected){
            paint.setColor(selected ? selectedColor : normalColor);
            canvas.drawRoundRect(rect, itemRadius, itemRadius, paint);
        }
    

    这里把drawItem抽成单独的方法,而且是protected,一般这种Indicator的不同只是图形不同,这样如果你需要简单扩展的话,改这个方法就可以了。
    到这里,自定义小控件就写完了,上一张效果图:

    最终效果
    本项目地址:仿写朴朴app

    相关文章

      网友评论

          本文标题:仿写朴朴app(二)

          本文链接:https://www.haomeiwen.com/subject/hehbvqtx.html