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
- ViewPagerIndicator-master源码分析 3
- ViewPagerIndicator-master源码分析 1
- ViewPagerIndicator-master源码分析 2
- 开源项目ViewPagerIndicator源码分析
- spark core源码分析3 Master HA
- spark core源码分析3 Master HA
- disruptor---master源码分析
- spark源码学习(二)---Master源码分析(3)-master对driver、executor的调度
- Nginx源码分析--master进程
- Spark源码分析-master启动
- spark 1.6.0 core源码分析3 Master HA
- Mesos源码分析(3): Mesos Master的启动之二
- ViewPagerindicator 源码解析
- ViewPagerindicator 源码解析
- ViewPagerindicator 源码解析
- ViewPagerindicator 源码解析
- ViewPagerindicator 源码解析
- ViewPagerindicator 源码解析
- Pascal's Triangle II
- 第四十四天 指南针、百度地图
- 20150922——第一个项目总结
- CRM-BaseDao的抽取
- 广联达2016西安研发笔试题
- ViewPagerIndicator-master源码分析 3
- BestCoder#56 Clarke and puzzle
- hdu acm 2569
- react的入门讲解
- listview加载更多
- LeetCode(40) Median of Two Sorted Arrays (两排序数组中位数)
- HDOJ 题目1158Employment Planning(DP)
- Android APP性能优化和内存优化
- HTML5学习笔记之audio标签