ViewPagerIndicator-master源码分析 3

来源:互联网 发布:阿里云服务器学生认证 编辑:程序博客网 时间:2024/05/27 03:29

1 TitlePageIndicator

效果图

public class TitlePageIndicator extends View implements PageIndicator {    /**     * Percentage indicating what percentage of the screen width away from     * center should the underline be fully faded. A value of 0.25 means that     * halfway between the center of the screen and an edge.     * 指示条和透明度的变化区间值     */    private static final float SELECTION_FADE_PERCENTAGE = 0.25f;    /**     * Percentage indicating what percentage of the screen width away from     * center should the selected text bold turn off. A value of 0.05 means     * that 10% between the center and an edge.--中心到边界     * 粗体的变化区间值,     */    private static final float BOLD_FADE_PERCENTAGE = 0.05f;    // 枚举的典型例子    public enum IndicatorStyle {        None(0), Triangle(1), Underline(2);        public final int value;        private IndicatorStyle(int value) {            this.value = value;        }        public static IndicatorStyle fromValue(int value) {            for (IndicatorStyle style : IndicatorStyle.values()) {                if (style.value == value) {                    return style;                }            }            return null;        }    }    public enum LinePosition {        Bottom(0), Top(1);        public final int value;        private LinePosition(int value) {            this.value = value;        }        public static LinePosition fromValue(int value) {            for (LinePosition position : LinePosition.values()) {                if (position.value == value) {                    return position;                }            }            return null;        }    }    private ViewPager mViewPager;    private ViewPager.OnPageChangeListener mListener;    private int mCurrentPage = -1;    private float mPageOffset;    private int mScrollState;    private final Paint mPaintText = new Paint();    private boolean mBoldText;    private int mColorText;    private int mColorSelected;    private Path mPath = new Path();    private final Rect mBounds = new Rect();    private final Paint mPaintFooterLine = new Paint();    private IndicatorStyle mFooterIndicatorStyle;    private LinePosition mLinePosition;    private final Paint mPaintFooterIndicator = new Paint();    private float mFooterIndicatorHeight;    private float mFooterIndicatorUnderlinePadding;    private float mFooterPadding;    private float mTitlePadding;    private float mTopPadding;    /** Left and right side padding for not active view titles. */    private float mClipPadding;    private float mFooterLineHeight;    private static final int INVALID_POINTER = -1;    private int mTouchSlop;    private float mLastMotionX = -1;    private int mActivePointerId = INVALID_POINTER;    private boolean mIsDragging;    public TitlePageIndicator(Context context) {        this(context, null);    }    public TitlePageIndicator(Context context, AttributeSet attrs) {        this(context, attrs, R.attr.vpiTitlePageIndicatorStyle);    }    public TitlePageIndicator(Context context, AttributeSet attrs, int defStyle) {        super(context, attrs, defStyle);        if (isInEditMode()) return;// edit mode        //Load defaults from resources        final Resources res = getResources();        // 赋值略...        mPaintText.setTextSize(textSize);        mPaintText.setAntiAlias(true);        // Geometry and text drawn with this style will be both filled and stroked at the same time        mPaintFooterLine.setStyle(Paint.Style.FILL_AND_STROKE);         mPaintFooterLine.setStrokeWidth(mFooterLineHeight);        mPaintFooterLine.setColor(footerColor);        mPaintFooterIndicator.setStyle(Paint.Style.FILL_AND_STROKE);        mPaintFooterIndicator.setColor(footerColor);        final ViewConfiguration configuration = ViewConfiguration.get(context);        mTouchSlop = ViewConfigurationCompat.getScaledPagingTouchSlop(configuration);    }    @Override    protected void onDraw(Canvas canvas) {        super.onDraw(canvas);        if (mViewPager == null) {            return;        }        final int count = mViewPager.getAdapter().getCount();        if (count == 0) {            return;        }        // mCurrentPage is -1 on first start and after orientation changed. If so, retrieve the correct index from viewpager.        if (mCurrentPage == -1 && mViewPager != null) {            mCurrentPage = mViewPager.getCurrentItem();        }        //Calculate views bounds 计算所有的标题区间,重要!!!否则下面不好理解        ArrayList<Rect> bounds = calculateAllBounds(mPaintText);        final int boundsSize = bounds.size();        //Make sure we're on a page that still exists        if (mCurrentPage >= boundsSize) {            setCurrentItem(boundsSize - 1);            return;        }        final int countMinusOne = count - 1;        final float halfWidth = getWidth() / 2f;        final int left = getLeft();        final float leftClip = left + mClipPadding;//左边距        final int width = getWidth();        int height = getHeight();        final int right = left + width;        final float rightClip = right - mClipPadding;        int page = mCurrentPage;        float offsetPercent;        if (mPageOffset <= 0.5) {            offsetPercent = mPageOffset;        } else {            page += 1; // 说明正在从中心页向右滑动,中心页=mCurrentPage+1            // 或者向左滑动了超过了页面的一半距离            offsetPercent = 1 - mPageOffset;        }        final boolean currentSelected = (offsetPercent <= SELECTION_FADE_PERCENTAGE);//透明效果        final boolean currentBold = (offsetPercent <= BOLD_FADE_PERCENTAGE);//黑体        final float selectedPercent = (SELECTION_FADE_PERCENTAGE - offsetPercent) / SELECTION_FADE_PERCENTAGE;//百分比        //Verify if the current view must be clipped to the screen        Rect curPageBound = bounds.get(mCurrentPage);        float curPageWidth = curPageBound.right - curPageBound.left;        if (curPageBound.left < leftClip) {            //Try to clip to the screen (left side)            clipViewOnTheLeft(curPageBound, curPageWidth, left);        }        if (curPageBound.right > rightClip) {            //Try to clip to the screen (right side)            clipViewOnTheRight(curPageBound, curPageWidth, right);        }        //Left views starting from the current position 裁剪左边view        if (mCurrentPage > 0) {            for (int i = mCurrentPage - 1; i >= 0; i--) {                Rect bound = bounds.get(i);                //Is left side is outside the screen                if (bound.left < leftClip) {                    int w = bound.right - bound.left;                    //Try to clip to the screen (left side)                    clipViewOnTheLeft(bound, w, left);                    //Except if there's an intersection with the right view                    Rect rightBound = bounds.get(i + 1);                    //Intersection                    if (bound.right + mTitlePadding > rightBound.left) {                        bound.left = (int) (rightBound.left - w - mTitlePadding);                        bound.right = bound.left + w;                    }                }            }        }        //Right views starting from the current position裁剪右边view        if (mCurrentPage < countMinusOne) {            for (int i = mCurrentPage + 1 ; i < count; i++) {                Rect bound = bounds.get(i);                //If right side is outside the screen                if (bound.right > rightClip) {                    int w = bound.right - bound.left;                    //Try to clip to the screen (right side)                    clipViewOnTheRight(bound, w, right);                    //Except if there's an intersection with the left view                    Rect leftBound = bounds.get(i - 1);                    //Intersection                    if (bound.left - mTitlePadding < leftBound.right) {                        bound.left = (int) (leftBound.right + mTitlePadding);                        bound.right = bound.left + w;                    }                }            }        }        //Now draw views        int colorTextAlpha = mColorText >>> 24;        for (int i = 0; i < count; i++) {            //Get the title            Rect bound = bounds.get(i);            //Only if one side is visible 只画显示出来的            if ((bound.left > left && bound.left < right) || (bound.right > left && bound.right < right)) {                final boolean currentPage = (i == page); //page是上面计算的,指明了那个标题该...                final CharSequence pageTitle = getTitle(i);                //Only set bold if we are within bounds----setFakeBoldText                mPaintText.setFakeBoldText(currentPage && currentBold && mBoldText);                //Draw text as unselected                mPaintText.setColor(mColorText);                if(currentPage && currentSelected) {                    //Fade out/in unselected text as the selected text fades in/out                    mPaintText.setAlpha(colorTextAlpha - (int)(colorTextAlpha * selectedPercent));                }                //Except if there's an intersection with the right view ? 再次检测                if (i < boundsSize - 1)  {                    Rect rightBound = bounds.get(i + 1);                    //Intersection                    if (bound.right + mTitlePadding > rightBound.left) {                        int w = bound.right - bound.left;                        bound.left = (int) (rightBound.left - w - mTitlePadding);                        bound.right = bound.left + w;                    }                }                canvas.drawText(pageTitle, 0, pageTitle.length(), bound.left, bound.bottom + mTopPadding, mPaintText);                //If we are within the selected bounds draw the selected text                if (currentPage && currentSelected) {                    mPaintText.setColor(mColorSelected);                    // 渐变的字体颜色                    mPaintText.setAlpha((int)((mColorSelected >>> 24) * selectedPercent));                    canvas.drawText(pageTitle, 0, pageTitle.length(), bound.left, bound.bottom + mTopPadding, mPaintText);                }            }        }        //If we want the line on the top change height to zero and invert the line height to trick the drawing code        float footerLineHeight = mFooterLineHeight;        float footerIndicatorLineHeight = mFooterIndicatorHeight;        if (mLinePosition == LinePosition.Top) {            height = 0;            footerLineHeight = -footerLineHeight;            footerIndicatorLineHeight = -footerIndicatorLineHeight;        }        //Draw the footer line        mPath.reset();        mPath.moveTo(0, height - footerLineHeight / 2f);        mPath.lineTo(width, height - footerLineHeight / 2f);        mPath.close();        canvas.drawPath(mPath, mPaintFooterLine);        float heightMinusLine = height - footerLineHeight;        switch (mFooterIndicatorStyle) {            case Triangle:                mPath.reset();                mPath.moveTo(halfWidth, heightMinusLine - footerIndicatorLineHeight);                mPath.lineTo(halfWidth + footerIndicatorLineHeight, heightMinusLine);                mPath.lineTo(halfWidth - footerIndicatorLineHeight, heightMinusLine);                mPath.close();                canvas.drawPath(mPath, mPaintFooterIndicator);                break;            case Underline:                if (!currentSelected || page >= boundsSize) {                    break;                }                Rect underlineBounds = bounds.get(page);                final float rightPlusPadding = underlineBounds.right + mFooterIndicatorUnderlinePadding;                final float leftMinusPadding = underlineBounds.left - mFooterIndicatorUnderlinePadding;                final float heightMinusLineMinusIndicator = heightMinusLine - footerIndicatorLineHeight;                mPath.reset();                mPath.moveTo(leftMinusPadding, heightMinusLine);                mPath.lineTo(rightPlusPadding, heightMinusLine);                mPath.lineTo(rightPlusPadding, heightMinusLineMinusIndicator);                mPath.lineTo(leftMinusPadding, heightMinusLineMinusIndicator);                mPath.close();                mPaintFooterIndicator.setAlpha((int)(0xFF * selectedPercent));                canvas.drawPath(mPath, mPaintFooterIndicator);                mPaintFooterIndicator.setAlpha(0xFF);                break;        }    }    // 基本和之前一样,留着看吧    public boolean onTouchEvent(android.view.MotionEvent ev) {        if (super.onTouchEvent(ev)) {            return true;        }        if ((mViewPager == null) || (mViewPager.getAdapter().getCount() == 0)) {            return false;        }        final int action = ev.getAction() & MotionEventCompat.ACTION_MASK;        switch (action) {            case MotionEvent.ACTION_DOWN:                mActivePointerId = MotionEventCompat.getPointerId(ev, 0);                mLastMotionX = ev.getX();                break;            case MotionEvent.ACTION_MOVE: {                final int activePointerIndex = MotionEventCompat.findPointerIndex(ev, mActivePointerId);                final float x = MotionEventCompat.getX(ev, activePointerIndex);                final float deltaX = x - mLastMotionX;                if (!mIsDragging) {                    if (Math.abs(deltaX) > mTouchSlop) {                        mIsDragging = true;                    }                }                if (mIsDragging) {// 重要!!                    mLastMotionX = x;                    if (mViewPager.isFakeDragging() || mViewPager.beginFakeDrag()) {                        mViewPager.fakeDragBy(deltaX);                    }                }                break;            }            case MotionEvent.ACTION_CANCEL:            case MotionEvent.ACTION_UP:                if (!mIsDragging) {                    final int count = mViewPager.getAdapter().getCount();                    final int width = getWidth();                    final float halfWidth = width / 2f;                    final float sixthWidth = width / 6f;                    final float leftThird = halfWidth - sixthWidth;                    final float rightThird = halfWidth + sixthWidth;                    final float eventX = ev.getX();                    if (eventX < leftThird) {                        if (mCurrentPage > 0) {                            if (action != MotionEvent.ACTION_CANCEL) {                                mViewPager.setCurrentItem(mCurrentPage - 1);                            }                            return true;                        }                    } else if (eventX > rightThird) {                        if (mCurrentPage < count - 1) {                            if (action != MotionEvent.ACTION_CANCEL) {                                mViewPager.setCurrentItem(mCurrentPage + 1);                            }                            return true;                        }                    } else {                        //Middle third                        if (mCenterItemClickListener != null && action != MotionEvent.ACTION_CANCEL) {                            mCenterItemClickListener.onCenterItemClick(mCurrentPage);                        }                    }                }                mIsDragging = false;                mActivePointerId = INVALID_POINTER;                if (mViewPager.isFakeDragging()) mViewPager.endFakeDrag();                break;            case MotionEventCompat.ACTION_POINTER_DOWN: {                final int index = MotionEventCompat.getActionIndex(ev);                mLastMotionX = MotionEventCompat.getX(ev, index);                mActivePointerId = MotionEventCompat.getPointerId(ev, index);                break;            }            case MotionEventCompat.ACTION_POINTER_UP:                final int pointerIndex = MotionEventCompat.getActionIndex(ev);                final int pointerId = MotionEventCompat.getPointerId(ev, pointerIndex);                if (pointerId == mActivePointerId) {                    final int newPointerIndex = pointerIndex == 0 ? 1 : 0;                    mActivePointerId = MotionEventCompat.getPointerId(ev, newPointerIndex);                }                mLastMotionX = MotionEventCompat.getX(ev, MotionEventCompat.findPointerIndex(ev, mActivePointerId));                break;        }        return true;    }    /**     * Set bounds for the right textView including clip padding.     *     * @param curViewBound     *            current bounds.     * @param curViewWidth     *            width of the view.     */    private void clipViewOnTheRight(Rect curViewBound, float curViewWidth, int right) {        curViewBound.right = (int) (right - mClipPadding);        curViewBound.left = (int) (curViewBound.right - curViewWidth);    }    /**     * Set bounds for the left textView including clip padding.     *     * @param curViewBound     *            current bounds.     * @param curViewWidth     *            width of the view.     */    private void clipViewOnTheLeft(Rect curViewBound, float curViewWidth, int left) {        curViewBound.left = (int) (left + mClipPadding);        curViewBound.right = (int) (mClipPadding + curViewWidth);    }    /**     * Calculate views bounds and scroll them according to the current index     *     * @param paint     * @return     */    private ArrayList<Rect> calculateAllBounds(Paint paint) {        ArrayList<Rect> list = new ArrayList<Rect>();        //For each views (If no values then add a fake one)        final int count = mViewPager.getAdapter().getCount();        final int width = getWidth();        final int halfWidth = width / 2;        for (int i = 0; i < count; i++) {            Rect bounds = calcBounds(i, paint);            int w = bounds.right - bounds.left;            int h = bounds.bottom - bounds.top;            // 1 当静止的时候mPageOffset=0,如果i == mCurrentPage,此时bounds.left的值就是将此子view放于父view中心的位置            // 2 静止后,如果向左轻轻滑动,那么i == mCurrentPage,- mPageOffset * width的绝对值就是子view左移的长度            // 3 静止后,如果向右滑动,那么i = mCurrentPage + 1,因此(1- mPageOffset * width)的值就是子view右移的长度,,            // ,此时,在刚开始滑动时mPageOffset的值是接近1的,因此右移的距离很小,而后,右滑增大,mPageOffset的值也变小,            //,,因此(1- mPageOffset * width)的值就变大,即向右滑动增大了。            // 这样实现了跟随viewpager的滑动            bounds.left = (int)(halfWidth - (w / 2f) + ((i - mCurrentPage - mPageOffset) * width));            bounds.right = bounds.left + w;            bounds.top = 0;            bounds.bottom = h;            list.add(bounds);        }        return list;    }    /**     * Calculate the bounds for a view's title     *     * @param index     * @param paint     * @return     */    private Rect calcBounds(int index, Paint paint) {        //Calculate the text bounds        Rect bounds = new Rect();        CharSequence title = getTitle(index);        bounds.right = (int) paint.measureText(title, 0, title.length());        bounds.bottom = (int) (paint.descent() - paint.ascent());        return bounds;    }    @Override    public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {        mCurrentPage = position; //随时更新的        mPageOffset = positionOffset;        invalidate();        if (mListener != null) {            mListener.onPageScrolled(position, positionOffset, positionOffsetPixels);        }    }    @Override    public void onPageSelected(int position) {        if (mScrollState == ViewPager.SCROLL_STATE_IDLE) {            mCurrentPage = position;//静止时才会赋值            invalidate();        }        if (mListener != null) {            mListener.onPageSelected(position);        }    }    @Override    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {        //Measure our width in whatever mode specified        final int measuredWidth = MeasureSpec.getSize(widthMeasureSpec);        //Determine our height        float height;        final int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);        if (heightSpecMode == MeasureSpec.EXACTLY) {            //We were told how big to be            height = MeasureSpec.getSize(heightMeasureSpec);        } else {            //Calculate the text bounds            mBounds.setEmpty();            mBounds.bottom = (int) (mPaintText.descent() - mPaintText.ascent());            height = mBounds.bottom - mBounds.top + mFooterLineHeight + mFooterPadding + mTopPadding;            if (mFooterIndicatorStyle != IndicatorStyle.None) {                height += mFooterIndicatorHeight;            }        }        final int measuredHeight = (int)height;        setMeasuredDimension(measuredWidth, measuredHeight);    }    @Override    public void onRestoreInstanceState(Parcelable state) {   // 和前面一样,自定义view时最好写这个方法去维护变量值}

说明,除了拖动viewpager带动标题栏滚动时,onTouch事件也支持动态改变viewpager的页面,从而
带动标题栏的滑动。可以通过修改,calculateAllBounds中可以修改title间的距离,标题们可以同时多个显示在屏幕上,
并且可以修改比例值改变黑体和透明的变化范围。最终效果可以类似苹果拍照应用的标题效果

2 UnderlinePageIndicator

拖动时显示,出来,之后渐变消失掉

public class UnderlinePageIndicator extends View implements PageIndicator {    private static final int INVALID_POINTER = -1;    private static final int FADE_FRAME_MS = 30;    private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);    private boolean mFades;//是否允许渐消失    private int mFadeDelay;//当停止后多久开始执行隐藏    private int mFadeLength;//经过多长时间消失完全    private int mFadeBy;//每次消失多少    private ViewPager mViewPager;    private ViewPager.OnPageChangeListener mListener;    private int mScrollState;    private int mCurrentPage;    private float mPositionOffset;    private int mTouchSlop;    private float mLastMotionX = -1;    private int mActivePointerId = INVALID_POINTER;    private boolean mIsDragging;    private final Runnable mFadeRunnable = new Runnable() {      @Override public void run() {        if (!mFades) return;        final int alpha = Math.max(mPaint.getAlpha() - mFadeBy, 0);        mPaint.setAlpha(alpha);        invalidate();        if (alpha > 0) {          postDelayed(this, FADE_FRAME_MS);        }      }    };    public UnderlinePageIndicator(Context context) {        this(context, null);    }    public UnderlinePageIndicator(Context context, AttributeSet attrs) {        this(context, attrs, R.attr.vpiUnderlinePageIndicatorStyle);    }    public UnderlinePageIndicator(Context context, AttributeSet attrs, int defStyle) {        super(context, attrs, defStyle);        if (isInEditMode()) return;        final Resources res = getResources();        // ...        setFades(a.getBoolean(R.styleable.UnderlinePageIndicator_fades, defaultFades));        setSelectedColor(a.getColor(R.styleable.UnderlinePageIndicator_selectedColor, defaultSelectedColor));        setFadeDelay(a.getInteger(R.styleable.UnderlinePageIndicator_fadeDelay, defaultFadeDelay));        setFadeLength(a.getInteger(R.styleable.UnderlinePageIndicator_fadeLength, defaultFadeLength));        final ViewConfiguration configuration = ViewConfiguration.get(context);        mTouchSlop = ViewConfigurationCompat.getScaledPagingTouchSlop(configuration);    }    public void setFades(boolean fades) {        if (fades != mFades) {            mFades = fades;            if (fades) {                post(mFadeRunnable);            } else {                removeCallbacks(mFadeRunnable);                mPaint.setAlpha(0xFF);                invalidate();            }        }    }    // mFadeLength / FADE_FRAME_MS 表示执行几次    public void setFadeLength(int fadeLength) {        mFadeLength = fadeLength;        mFadeBy = 0xFF / (mFadeLength / FADE_FRAME_MS);    }    @Override    protected void onDraw(Canvas canvas) {        super.onDraw(canvas);        if (mViewPager == null) {            return;        }        final int count = mViewPager.getAdapter().getCount();        if (count == 0) {            return;        }        if (mCurrentPage >= count) {            setCurrentItem(count - 1);            return;        }        final int paddingLeft = getPaddingLeft();        final float pageWidth = (getWidth() - paddingLeft - getPaddingRight()) / (1f * count);        final float left = paddingLeft + pageWidth * (mCurrentPage + mPositionOffset);        final float right = left + pageWidth;        final float top = getPaddingTop();        final float bottom = getHeight() - getPaddingBottom();        canvas.drawRect(left, top, right, bottom, mPaint);    }    public boolean onTouchEvent(MotionEvent ev) {        // 高度太小,操作很不便。。。,但还是有了,和之前一样        return true;    }    @Override    public void setViewPager(ViewPager viewPager) {        if (mViewPager == viewPager) {            return;        }        if (mViewPager != null) {            //Clear us from the old pager.            mViewPager.setOnPageChangeListener(null);        }        if (viewPager.getAdapter() == null) {            throw new IllegalStateException("ViewPager does not have adapter instance.");        }        mViewPager = viewPager;        mViewPager.setOnPageChangeListener(this);        invalidate();        post(new Runnable() {            @Override public void run() {                if (mFades) {                    post(mFadeRunnable);                }            }        });    }    @Override    public void onPageScrollStateChanged(int state) {        mScrollState = state;        if (mListener != null) {            mListener.onPageScrollStateChanged(state);        }    }    @Override    public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {        mCurrentPage = position;        mPositionOffset = positionOffset;        if (mFades) {            if (positionOffsetPixels > 0) {//表示在滑动,不用执行                removeCallbacks(mFadeRunnable);                mPaint.setAlpha(0xFF);            } else if (mScrollState != ViewPager.SCROLL_STATE_DRAGGING) {//可以执行了                postDelayed(mFadeRunnable, mFadeDelay);            }        }        invalidate();        if (mListener != null) {            mListener.onPageScrolled(position, positionOffset, positionOffsetPixels);        }    }    @Override    public void onPageSelected(int position) {        if (mScrollState == ViewPager.SCROLL_STATE_IDLE) {//测试发现一直不满足。。。            mCurrentPage = position;            mPositionOffset = 0;            invalidate();            mFadeRunnable.run();        }        if (mListener != null) {            mListener.onPageSelected(position);        }    }    @Override    public void onRestoreInstanceState(Parcelable state) {       // 。。。。}
0 0
原创粉丝点击