天天看点

ViewPagerIndicator-master源码分析 21 IcsLinearLayout 2 IconPageIndicator3 LinePageIndicator4 TabPageIndicator

1 IcsLinearLayout

首先看一下自定义的图标封装器,如果主题中配置了属性,则会在每个子view之间绘制分隔符

官方 support.v7 中已经添加了 一个类android.support.v7.widget.LinearLayoutCompat 添加了对低版本 divider 的支持;

父类的构造通过setDividerDrawable为mDivider进行赋值,但是我们子类复写了这个方法,运行时调用的会是子类的方法,那么父类的这个方法自然就未执行,父类的mDivider为null了~~~;

关键应该是 private Drawable mDivider; 是private吧;

自定义的LinerLyout如何获取新的属性;

class IcsLinearLayout extends LinearLayout {
    private static final int[] LL = new int[] { // 要获取的属性集合
        /* 0 */ android.R.attr.divider,
        /* 1 */ android.R.attr.showDividers,
        /* 2 */ android.R.attr.dividerPadding,
    };
    private static final int LL_DIVIDER = ;// 属性集合中各属性的index
    private static final int LL_SHOW_DIVIDER = ;
    private static final int LL_DIVIDER_PADDING = ;

    private Drawable mDivider;
    private int mDividerWidth;
    private int mDividerHeight;
    private int mShowDividers;
    private int mDividerPadding;

    public IcsLinearLayout(Context context, int themeAttr) {
        super(context);
        // 第一个参数为null,因此只能从themeAttr即Activity的主题中去获取属性值了
        TypedArray a = context.obtainStyledAttributes(null, LL, themeAttr, );
        setDividerDrawable(a.getDrawable(IcsLinearLayout.LL_DIVIDER));
        mDividerPadding = a.getDimensionPixelSize(LL_DIVIDER_PADDING, );// 分隔符的上下与view的间隔
        mShowDividers = a.getInteger(LL_SHOW_DIVIDER, SHOW_DIVIDER_NONE);// 分隔符的类型如middle
        a.recycle();
    }

    public void setDividerDrawable(Drawable divider) {
        if (divider == mDivider) {
            return;
        }
        mDivider = divider;
        if (divider != null) {
            mDividerWidth = divider.getIntrinsicWidth();
            mDividerHeight = divider.getIntrinsicHeight();
        } else {
            mDividerWidth = ;
            mDividerHeight = ;
        }
        setWillNotDraw(divider == null); // 又看到了。。。
        requestLayout();
    }

    @Override
    protected void measureChildWithMargins(View child, int parentWidthMeasureSpec, int widthUsed, int parentHeightMeasureSpec, int heightUsed) { // 测试child的外边距
        final int index = indexOfChild(child);
        final int orientation = getOrientation();
        final LayoutParams params = (LayoutParams) child.getLayoutParams();
        if (hasDividerBeforeChildAt(index)) {// 满足时添加margin
            if (orientation == VERTICAL) {
                //Account for the divider by pushing everything up
                params.topMargin = mDividerHeight;
            } else {
                //Account for the divider by pushing everything left
                params.leftMargin = mDividerWidth;
            }
        }

        final int count = getChildCount();
        if (index == count - ) {
            if (hasDividerBeforeChildAt(count)) { //不满足
                if (orientation == VERTICAL) {
                    params.bottomMargin = mDividerHeight;
                } else {
                    params.rightMargin = mDividerWidth;
                }
            }
        }
        super.measureChildWithMargins(child, parentWidthMeasureSpec, widthUsed, parentHeightMeasureSpec, heightUsed);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        if (mDivider != null) {
            if (getOrientation() == VERTICAL) {
                drawDividersVertical(canvas);
            } else {
                drawDividersHorizontal(canvas);
            }
        }
        super.onDraw(canvas);
    }

    private void drawDividersVertical(Canvas canvas) {
        final int count = getChildCount();
        for (int i = ; i < count; i++) {
            final View child = getChildAt(i);
            if (child != null && child.getVisibility() != GONE) {
                if (hasDividerBeforeChildAt(i)) {
                    final android.widget.LinearLayout.LayoutParams lp = (android.widget.LinearLayout.LayoutParams) child.getLayoutParams();
                    final int top = child.getTop() - lp.topMargin/* - mDividerHeight*/;
                    drawHorizontalDivider(canvas, top);
                }
            }
        }
        if (hasDividerBeforeChildAt(count)) {
            final View child = getChildAt(count - );
            int bottom = ;
            if (child == null) {
                bottom = getHeight() - getPaddingBottom() - mDividerHeight;
            } else {
                //final LayoutParams lp = (LayoutParams) child.getLayoutParams();
                bottom = child.getBottom()/* + lp.bottomMargin*/;
            }
            drawHorizontalDivider(canvas, bottom);
        }
    }

    private void drawDividersHorizontal(Canvas canvas) {
        final int count = getChildCount();
        for (int i = ; i < count; i++) {
            final View child = getChildAt(i);
            if (child != null && child.getVisibility() != GONE) {
                if (hasDividerBeforeChildAt(i)) {
                    final android.widget.LinearLayout.LayoutParams lp = (android.widget.LinearLayout.LayoutParams) child.getLayoutParams();
                    // 计算分隔符的起始位置
                    final int left = child.getLeft() - lp.leftMargin/* - mDividerWidth*/;
                    drawVerticalDivider(canvas, left);//在margin的区域绘制drawable-Divider
                }
            }
        }
        if (hasDividerBeforeChildAt(count)) {//不满足
            final View child = getChildAt(count - );
            int right = ;
            if (child == null) {
                right = getWidth() - getPaddingRight() - mDividerWidth;
            } else { // 在所有图标的最右侧也绘制Divider
                //final LayoutParams lp = (LayoutParams) child.getLayoutParams();
                right = child.getRight()/* + lp.rightMargin*/;
            }
            drawVerticalDivider(canvas, right);
        }
    }

    private void drawHorizontalDivider(Canvas canvas, int top) {
        mDivider.setBounds(getPaddingLeft() + mDividerPadding, top,
                getWidth() - getPaddingRight() - mDividerPadding, top + mDividerHeight);
        mDivider.draw(canvas);
    }
    // 首先要设置绘制范围!!!可以看到mDividerPadding的使用
    private void drawVerticalDivider(Canvas canvas, int left) { 
        mDivider.setBounds(left, getPaddingTop() + mDividerPadding,
                left + mDividerWidth, getHeight() - getPaddingBottom() - mDividerPadding);
        mDivider.draw(canvas);
    }
    private boolean hasDividerBeforeChildAt(int childIndex) {
        if (childIndex ==  || childIndex == getChildCount()) {
          return false;
        }
        if ((mShowDividers & SHOW_DIVIDER_MIDDLE) != ) {
            boolean hasVisibleViewBefore = false;
            for (int i = childIndex - ; i >= ; i--) {
                if (getChildAt(i).getVisibility() != GONE) {
                    hasVisibleViewBefore = true;
                    break;
                }
            }
            return hasVisibleViewBefore;
        }
        return false;
    }
}
           

2 IconPageIndicator

ViewPagerIndicator-master源码分析 21 IcsLinearLayout 2 IconPageIndicator3 LinePageIndicator4 TabPageIndicator
public class IconPageIndicator extends HorizontalScrollView implements PageIndicator {
    private final IcsLinearLayout mIconsLayout;//上面的类
    private ViewPager mViewPager;
    private OnPageChangeListener mListener;
    private Runnable mIconSelector;
    private int mSelectedIndex;
    public IconPageIndicator(Context context) {
        this(context, null);
    }
    public IconPageIndicator(Context context, AttributeSet attrs) {
        super(context, attrs);
        setHorizontalScrollBarEnabled(false);//vpiIconPageIndicatorStyle这个属性主题中没
        mIconsLayout = new IcsLinearLayout(context, R.attr.vpiIconPageIndicatorStyle);
        // 图标居中显示
        addView(mIconsLayout, new LayoutParams(WRAP_CONTENT, FILL_PARENT, Gravity.CENTER));
    }
    // 控制HorizontalScrollView的滑动,猜测:范围是大于0而小于内部view的最大宽度的,即左和右不会出现空白的,如果目的地会出现空白,则不执行应该
    private void animateToIcon(final int position) {
        final View iconView = mIconsLayout.getChildAt(position);
        if (mIconSelector != null) {
            removeCallbacks(mIconSelector);
        }
        mIconSelector = new Runnable() {
            public void run() {//getLeft()表示子view的左在父view的坐标
     // (getWidth() - iconView.getWidth()) / 2 表示中心位置,iconView.getLeft()相减后就是应该移动的距离了
               final int scrollPos = iconView.getLeft() - (getWidth() - iconView.getWidth()) / ;
                smoothScrollTo(scrollPos, );
                mIconSelector = null;
            }
        };
        post(mIconSelector);
    }

    @Override
    public void onAttachedToWindow() { // ssss
        super.onAttachedToWindow();
        if (mIconSelector != null) {
            // Re-post the selector we saved
            post(mIconSelector);
        }
    }
    @Override
    public void onDetachedFromWindow() {//sssss
        super.onDetachedFromWindow();
        if (mIconSelector != null) {
            removeCallbacks(mIconSelector);
        }
    }
    @Override
    public void onPageSelected(int arg0) {
        setCurrentItem(arg0);
    }
    @Override
    public void setViewPager(ViewPager view) {
        if (mViewPager == view) {
            return;
        }
        if (mViewPager != null) {
            mViewPager.setOnPageChangeListener(null);
        }
        PagerAdapter adapter = view.getAdapter();
        if (adapter == null) {
            throw new IllegalStateException("ViewPager does not have adapter instance.");
        }
        mViewPager = view;
        view.setOnPageChangeListener(this);
        notifyDataSetChanged();
    }

    public void notifyDataSetChanged() {
        mIconsLayout.removeAllViews();
        IconPagerAdapter iconAdapter = (IconPagerAdapter) mViewPager.getAdapter();
        int count = iconAdapter.getCount();
        for (int i = ; i < count; i++) {
            ImageView view = new ImageView(getContext(), null, R.attr.vpiIconPageIndicatorStyle);
            view.setImageResource(iconAdapter.getIconResId(i));
            mIconsLayout.addView(view);
        }
        if (mSelectedIndex > count) {
            mSelectedIndex = count - ;
        }
        setCurrentItem(mSelectedIndex);
        requestLayout();
    }

    @Override
    public void setViewPager(ViewPager view, int initialPosition) {
        setViewPager(view);
        setCurrentItem(initialPosition);
    }

    @Override
    public void setCurrentItem(int item) {
        if (mViewPager == null) {
            throw new IllegalStateException("ViewPager has not been bound.");
        }
        mSelectedIndex = item;
        mViewPager.setCurrentItem(item);
        int tabCount = mIconsLayout.getChildCount();
        for (int i = ; i < tabCount; i++) {
            View child = mIconsLayout.getChildAt(i);
            boolean isSelected = (i == item);
            child.setSelected(isSelected);
            if (isSelected) {
                animateToIcon(item);//如果不执行这个,那么当指示器大于屏幕宽度时,指示器不会自己滚动
            }
        }
    }

}
           

3 LinePageIndicator

横线指示器

ViewPagerIndicator-master源码分析 21 IcsLinearLayout 2 IconPageIndicator3 LinePageIndicator4 TabPageIndicator
protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        // ,,,
        final float lineWidthAndGap = mLineWidth + mGapWidth;
        final float indicatorWidth = (count * lineWidthAndGap) - mGapWidth;
        final float paddingTop = getPaddingTop();
        final float paddingLeft = getPaddingLeft();
        final float paddingRight = getPaddingRight();

        float verticalOffset = paddingTop + ((getHeight() - paddingTop - getPaddingBottom()) / f);
        float horizontalOffset = paddingLeft;
        if (mCentered) {
            horizontalOffset += ((getWidth() - paddingLeft - paddingRight) / f) - (indicatorWidth / f);
        }
        //Draw stroked circles
        for (int i = ; i < count; i++) {
            float dx1 = horizontalOffset + (i * lineWidthAndGap);
            float dx2 = dx1 + mLineWidth;
            canvas.drawLine(dx1, verticalOffset, dx2, verticalOffset, (i == mCurrentPage) ? mPaintSelected : mPaintUnselected);
        }
    }
        @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(measureWidth(widthMeasureSpec), measureHeight(heightMeasureSpec));
    }
    private int measureWidth(int measureSpec) {
        float result;
        int specMode = MeasureSpec.getMode(measureSpec);
        int specSize = MeasureSpec.getSize(measureSpec);
        if ((specMode == MeasureSpec.EXACTLY) || (mViewPager == null)) {
            //We were told how big to be
            result = specSize;
        } else {
            //Calculate the width according the views count
            final int count = mViewPager.getAdapter().getCount();
            result = getPaddingLeft() + getPaddingRight() + (count * mLineWidth) + ((count - ) * mGapWidth);
            //Respect AT_MOST value if that was what is called for by measureSpec
            if (specMode == MeasureSpec.AT_MOST) {
                result = Math.min(result, specSize);
            }
        }
        return (int)FloatMath.ceil(result); // good
    }

    /**
     * Determines the height of this view
     *
     * @param measureSpec
     *            A measureSpec packed into an int
     * @return The height of the view, honoring constraints from measureSpec
     */
    private int measureHeight(int measureSpec) {
        float result;
        int specMode = MeasureSpec.getMode(measureSpec);
        int specSize = MeasureSpec.getSize(measureSpec);

        if (specMode == MeasureSpec.EXACTLY) {
            //We were told how big to be
            result = specSize;
        } else {
            //Measure the height
            result = mPaintSelected.getStrokeWidth() + getPaddingTop() + getPaddingBottom();
            //Respect AT_MOST value if that was what is called for by measureSpec
            if (specMode == MeasureSpec.AT_MOST) {
                result = Math.min(result, specSize);
            }
        }
        return (int)FloatMath.ceil(result); // good
    }
           

4 TabPageIndicator

这是一般的

ViewPagerIndicator-master源码分析 21 IcsLinearLayout 2 IconPageIndicator3 LinePageIndicator4 TabPageIndicator
ViewPagerIndicator-master源码分析 21 IcsLinearLayout 2 IconPageIndicator3 LinePageIndicator4 TabPageIndicator
ViewPagerIndicator-master源码分析 21 IcsLinearLayout 2 IconPageIndicator3 LinePageIndicator4 TabPageIndicator

这是IcsLinearLayout起作用时的效果,tab直接都有分隔,需要主题配置属性了

ViewPagerIndicator-master源码分析 21 IcsLinearLayout 2 IconPageIndicator3 LinePageIndicator4 TabPageIndicator

这是带icon的效果

ViewPagerIndicator-master源码分析 21 IcsLinearLayout 2 IconPageIndicator3 LinePageIndicator4 TabPageIndicator
ViewPagerIndicator-master源码分析 21 IcsLinearLayout 2 IconPageIndicator3 LinePageIndicator4 TabPageIndicator

这里很厉害的是,包括icon什么的都是通过background属性搞定的!!!

以及下划线,下滑选中,按压效果,都是配置drawable实现的,厉害!!!

public class TabPageIndicator extends HorizontalScrollView implements PageIndicator {
    /** Title text used when no title is provided by the adapter. */
    private static final CharSequence EMPTY_TITLE = "";
    private Runnable mTabSelector;
    private final IcsLinearLayout mTabLayout;
    private ViewPager mViewPager;
    private ViewPager.OnPageChangeListener mListener;

    private int mMaxTabWidth;
    private int mSelectedTabIndex;

    private OnTabReselectedListener mTabReselectedListener;
    public TabPageIndicator(Context context) {
        this(context, null);
    }
    public TabPageIndicator(Context context, AttributeSet attrs) {
        super(context, attrs);
        setHorizontalScrollBarEnabled(false);
        mTabLayout = new IcsLinearLayout(context, R.attr.vpiTabPageIndicatorStyle);
        // 注意,此处设置的宽度为WRAP_CONTENT,但实际中的效果却总是MATCH_PARENT原因见下xxx
        addView(mTabLayout, new ViewGroup.LayoutParams(WRAP_CONTENT, MATCH_PARENT));
    }
    @Override
    public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        final boolean lockedExpanded = widthMode == MeasureSpec.EXACTLY;
        // xxx  Indicates this ScrollView whether it should stretch its content width to fill the viewport or not. 当它的子view宽度小于自己时,则拉伸子view的宽度,因此上述疑问解决。即拉伸了IcsLinearLayout的宽度和ScrollView一样了
        setFillViewport(lockedExpanded); // !!!

        final int childCount = mTabLayout.getChildCount();
        if (childCount >  && (widthMode == MeasureSpec.EXACTLY || widthMode == MeasureSpec.AT_MOST)) {
        // 计算textview的最大宽度值,当拉伸后,同样会检测的,0.4f这个临界值设置的很好,因为3个铺满为0.33,如果这里设置为0.3,那么由于拉伸了,所以每个宽度都为0.33左右,这时便大于0.3,会重新设置了,那么测试会发现右侧会有0.09空白区域的
            if (childCount > ) { 
                mMaxTabWidth = (int)(MeasureSpec.getSize(widthMeasureSpec) * f);
            } else {
                mMaxTabWidth = MeasureSpec.getSize(widthMeasureSpec) / ;
            }
        } else {
            mMaxTabWidth = -;
        }

        final int oldWidth = getMeasuredWidth();
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        final int newWidth = getMeasuredWidth();
        //onMeasure会执行两次,第一次的时候oldWidth != newWidth满足,第二次则相等了
        if (lockedExpanded && oldWidth != newWidth) {
            // Recenter the tab display if we're at a new (scrollable) size.
            setCurrentItem(mSelectedTabIndex);
        }
    }

    private void animateToTab(final int position) {
       // ... 同上
    }

    private void addTab(int index, CharSequence text, int iconResId) {
        final TabView tabView = new TabView(getContext());
        tabView.mIndex = index;
        tabView.setFocusable(true);
        tabView.setOnClickListener(mTabClickListener);
        tabView.setText(text);

        if (iconResId != ) {
            tabView.setCompoundDrawablesWithIntrinsicBounds(iconResId, , , );
        }
        // 注意次数设置的weight为1,宽度都相等的
        mTabLayout.addView(tabView, new LinearLayout.LayoutParams(, MATCH_PARENT, ));
    } 

    @Override
    public void setCurrentItem(int item) {
        if (mViewPager == null) {
            throw new IllegalStateException("ViewPager has not been bound.");
        }
        mSelectedTabIndex = item;
        mViewPager.setCurrentItem(item);

        final int tabCount = mTabLayout.getChildCount();
        for (int i = ; i < tabCount; i++) {
            final View child = mTabLayout.getChildAt(i);
            final boolean isSelected = (i == item);
            child.setSelected(isSelected);
            if (isSelected) {
                animateToTab(item);
            }
        }
    }
    private class TabView extends TextView {
        private int mIndex;
        public TabView(Context context) {
            super(context, null, R.attr.vpiTabPageIndicatorStyle);
        }
        @Override
        public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
            // 当拉伸后,宽度增大了,onMeasure同样会执行的,因此不会超越mMaxTabWidth
            // Re-measure if we went beyond our maximum size.
            if (mMaxTabWidth >  && getMeasuredWidth() > mMaxTabWidth) {
                super.onMeasure(MeasureSpec.makeMeasureSpec(mMaxTabWidth, MeasureSpec.EXACTLY),
                        heightMeasureSpec);
            }
        }
        public int getIndex() {
            return mIndex;
        }
    }
}
           

继续阅读