模仿PagerSlidingTabStrip - ViewPag

作者: qingwenLi | 来源:发表于2015-12-31 14:38 被阅读685次

    版权声明:本文为博主原创文章,未经博主允许不得转载。

    自从上班后一直以来项目用到比较有难度的控件都上github搜,从来没想过自己写。
    用多了别人的东西也觉得没什么意思能力也得不到提升,到了今年觉得一定要有自己的一套东西才行想法有啦就马上行动....
    这次的准备使用最常用的控件 "ViewPager 滑动视图Tab " 而之前一直都使用 “PagerSlidingTabStrip开源控件 ” 这个比较有名效果也比较好,
    今天呢就把它作为参考自己写一个 Sliding。

    这里的先给出完整的控件代码
    建议先看看 下面在单个方法讲解:

    public class SlidingTab extends HorizontalScrollView {
        
        private int textColor = 0x00808080;
        private int textDefColor = 0x00808080;
        private float textPadding = 15;
        private float textSize = 10;
        private float lineHeight = 10;
        private int lineColor = 0x00808080;
        private LinearLayout mLayout;
        private ViewPager mPager;
        private final PageListener mPageListener = new PageListener();
        private Paint mPaint; 
        private int count ;
        private float offset = 52;
        private int lastScrollX;
        private int currentPosition;
        private float  currentPositionOffset;
        private OnTabPageChangeListener onTabPageChangeListener;
        
        
        public SlidingTab(Context context) {
            super(context);
        }
        public SlidingTab(Context context, AttributeSet attrs) {
            super(context, attrs);
            init(attrs);
        }
    
        public SlidingTab(Context context, AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
            init(attrs);
        }
        
        
    
        private  void init(AttributeSet attrs){
            DisplayMetrics dm = getResources().getDisplayMetrics();
            offset = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, offset,dm);
            lineHeight = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, lineHeight,dm);
            textPadding = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, textPadding,dm);
            textSize = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, textSize,dm);
            
            
            
            TypedArray mTypedValue =  getContext().obtainStyledAttributes(attrs,R.styleable.SlingTab);
            textColor = mTypedValue.getColor(R.styleable.SlingTab_textColor, textColor);
            textDefColor  = mTypedValue.getColor(R.styleable.SlingTab_textDefColor, textDefColor);
            textPadding = mTypedValue.getDimension(R.styleable.SlingTab_textPadding, textPadding);
            textSize = mTypedValue.getDimension(R.styleable.SlingTab_textSize, textSize);
            lineHeight = mTypedValue.getDimension(R.styleable.SlingTab_lineHeight, lineHeight);
            lineColor  = mTypedValue.getColor(R.styleable.SlingTab_lineColor, lineColor);
            mTypedValue.recycle();
            mPaint = new Paint();
            mPaint.setStyle(Style.FILL);
            mPaint.setAntiAlias(true);
            setHorizontalScrollBarEnabled(false);
            mLayout = new LinearLayout(getContext());
            mLayout.setOrientation(LinearLayout.HORIZONTAL);
            mLayout.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));    
            addView(mLayout);
        }
        public void setViewPager(ViewPager mPager){
            if(mPager == null){
                throw new NullPointerException("current ViewPager for empty");
            }
            this.mPager = mPager;
            mPager.addOnPageChangeListener(mPageListener);
            notifyDataSetChanged();     
        }
        
        @Override
        protected void onDraw(Canvas canvas) {
            super.onDraw(canvas);   
            if(count < 0){
                return;
            }
            
    
            
             mPaint.setColor(lineColor);
             int height = getMeasuredHeight();
             float lineLeft = mLayout.getChildAt(currentPosition).getLeft() + textPadding;
             float lineRight = mLayout.getChildAt(currentPosition).getRight()- textPadding;
            if(currentPositionOffset > 0F && currentPosition < count){
                 float Left = mLayout.getChildAt(currentPosition + 1).getLeft()  +  textPadding;
                 float Right = mLayout.getChildAt(currentPosition + 1).getRight()-  textPadding;
                 lineLeft = (currentPositionOffset * Left + (1F - currentPositionOffset) * lineLeft);
                 lineRight = (currentPositionOffset * Right + (1F - currentPositionOffset) * lineRight);
            }
            canvas.drawRect(lineLeft , height - lineHeight, lineRight, height, mPaint);
        }
        
        
        
        private void notifyDataSetChanged() {
            count =  mPager.getAdapter().getCount();
            for (int i = 0; i < count; i++) {
                mLayout.addView(addTab(i, mPager.getAdapter().getPageTitle(i).toString()));
            }
            scrollToChild(0, 0);
        }
        
        
        
        private TextView addTab(final int position, String title) {
            TextView mTextView = new TextView(getContext());
            mTextView.setId(position);
            mTextView.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT));
            mTextView.setGravity(Gravity.CENTER);
            mTextView.setText(title);
            mTextView.setTextSize(TypedValue.COMPLEX_UNIT_PX,textSize);
            mTextView.setTextColor(textDefColor);
            mTextView.setPadding((int)textPadding, 0, (int)textPadding, 0);
            mTextView.setOnClickListener(new OnClickListener() {
                @Override
                public void onClick(View v) {
                    mPager.setCurrentItem(position);
                }
            });
            return mTextView;
        }
        
        
        
        
        
        void scrollToChild(int position , int positionOffset){
            if(count < 0 ){
                return;
            }
            int newPositionOffset  = mLayout.getChildAt(position).getLeft() + positionOffset;
            if(position > 0 || positionOffset > 0 ){
                newPositionOffset -= offset;
            }
            if(newPositionOffset != lastScrollX){
                lastScrollX = newPositionOffset;
                scrollTo(newPositionOffset, 0);
            }           
        }
        
        
        
        public  interface OnTabPageChangeListener{
            void onPageScrollStateChanged(int state);
            void onPageScrolled(int position, float positionOffset, int positionOffsetPixels);
            void onPageSelected(int position);
        } 
    
        
        class PageListener implements OnPageChangeListener{
        
            @Override
            public void onPageScrollStateChanged(int state) {
                if(onTabPageChangeListener != null){
                    onTabPageChangeListener.onPageScrollStateChanged(state);
                }   
            }
            @Override
            public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
                SlidingTab.this.currentPosition = position;
                SlidingTab.this.currentPositionOffset = positionOffset;
                scrollToChild(position, (int) (positionOffset * mLayout.getChildAt(position).getWidth()) );
                invalidate();
                if(onTabPageChangeListener != null){
                    onTabPageChangeListener.onPageScrolled(position, positionOffset, positionOffsetPixels);
                }
            }
             
            @Override
            public void onPageSelected(int position) {
    
                if(onTabPageChangeListener != null){
                    onTabPageChangeListener.onPageSelected(position );
                }
            
            }
            
        }
        public int getTextColor() {
            return textColor;
        }
        public void setTextColor(int textColor) {
            this.textColor = textColor;
            invalidate();
        }
        public int getTextDefColor() {
            return textDefColor;
        }
        public void setTextDefColor(int textDefColor) {
            this.textDefColor = textDefColor;
            invalidate();
        }
        public float getTextPadding() {
            return textPadding;
        }
        public void setTextPadding(float textPadding) {
            this.textPadding = textPadding;
            invalidate();
        }
        public float getTextSize() {
            return textSize;
        }
        public void setTextSize(float textSize) {
            this.textSize = textSize;
            invalidate();
        }
        public float getLineHeight() {
            return lineHeight;
        }
        public void setLineHeight(float lineHeight) {
            this.lineHeight = lineHeight;
            invalidate();
        }
        public int getLineColor() {
            return lineColor;
        }
        public void setLineColor(int lineColor) {
            this.lineColor = lineColor;
            invalidate();
        }
        public OnTabPageChangeListener getOnTabPageChangeListener() {
            return onTabPageChangeListener;
        }
        public void setOnTabPageChangeListener(
                OnTabPageChangeListener onTabPageChangeListener) {
            this.onTabPageChangeListener = onTabPageChangeListener;
        }
    }
    

    要写控件首先要想的是这控件具备什么属性。
    而我们这个控件的属性是:
    ViewPager 每页面对应一个标题,标题就是 Text ,Text有颜色大小
    ViewPager 页面对应的标识以线为基础。

          //标题颜色  : 这里是为了后期的扩展 
        private int textColor = 0x00808080;
         //默认标题颜色
        private int textDefColor = 0x00808080;
         // 每个标题的边距
        private float textPadding = 15;
         // 标题字体大小
        private float textSize = 10;
        // 页面标识 线的高度
        private float lineHeight = 10;
    

    当我们把控件具备的属性都了解清楚啦,并且都写出来啦,这个时候为了方便就需要自定义当前控件的标签属性,在res底下的values文件夹中创建一个attr.xml内容如下

    <?xml version="1.0" encoding="utf-8"?>
    <resources>
            <declare-styleable name="SlingTab">
                   <attr name="textColor" format="color" />
                   <attr name="textDefColor" format="color" />  
                   <attr name="textPadding" format="dimension" />   
                   <attr name="textSize" format="dimension" />  
                   <attr name="lineHeight" format="dimension" />    
                   <attr name="lineColor" format="color" /> 
            </declare-styleable>
    </resources>
    

    xml创建好啦,这个时候我们就要去引用这些属性啦让它可以成为标签的属性

            
        public SlidingTab(Context context) {
            super(context);
        }
        public SlidingTab(Context context, AttributeSet attrs) {
            super(context, attrs);
            init(attrs);
        }
    
        public SlidingTab(Context context, AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
            init(attrs);
        }
        
            private  void init(AttributeSet attrs){
            TypedArray mTypedValue =   getContext().obtainStyledAttributes(attrs,R.styleable.SlingTab);
            textColor =    mTypedValue.getColor(R.styleable.SlingTab_textColor, textColor);
            textDefColor  = mTypedValue.getColor(R.styleable.SlingTab_textDefColor, textDefColor);
            textPadding = mTypedValue.getDimension(R.styleable.SlingTab_textPadding, textPadding);
            textSize = mTypedValue.getDimension(R.styleable.SlingTab_textSize, textSize);
            lineHeight = mTypedValue.getDimension(R.styleable.SlingTab_lineHeight, lineHeight);
            lineColor  = mTypedValue.getColor(R.styleable.SlingTab_lineColor, lineColor);
            mTypedValue.recycle();
        }
    

    接下来就创建线的画笔,而当前控件是继承至 HorizontalScrollView只能有一个子View那么就将Linearlayout作为子布局。

    
    mPaint = new Paint(); 
    mPaint.setStyle(Style.FILL); 
    mPaint.setAntiAlias(true); 
    setHorizontalScrollBarEnabled(false); 
    mLayout = new LinearLayout(getContext()); 
    mLayout.setOrientation(LinearLayout.HORIZONTAL); 
    mLayout.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)); 
    addView(mLayout);
    

    接着传入进来的ViewPager通过ViewPager获取标题名称以及标题个数然后创建TextView,设置ViewPager 滑动监听为了实现后期滑动的标示偏移值以及TextView实现点击事件。

    
        
        public void setViewPager(ViewPager mPager){
            if(mPager == null){
                throw new NullPointerException("current ViewPager for empty");
            }
            this.mPager = mPager;
            mPager.addOnPageChangeListener(mPageListener);
            notifyDataSetChanged();     
        }
        
        
        private void notifyDataSetChanged() {
            count =  mPager.getAdapter().getCount();
            for (int i = 0; i < count; i++) {
                mLayout.addView(addTab(i, mPager.getAdapter().getPageTitle(i).toString()));
            }
            scrollToChild(0, 0);
        }
        
        
        
        private TextView addTab(final int position, String title) {
            TextView mTextView = new TextView(getContext());
            mTextView.setId(position);
            mTextView.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT));
            mTextView.setGravity(Gravity.CENTER);
            mTextView.setText(title);
            mTextView.setTextSize(TypedValue.COMPLEX_UNIT_PX,textSize);
            mTextView.setTextColor(textDefColor);
            mTextView.setPadding((int)textPadding, 0, (int)textPadding, 0);
            mTextView.setOnClickListener(new OnClickListener() {
                @Override
                public void onClick(View v) {
                    mPager.setCurrentItem(position);
                }
            });
            return mTextView;
        }
    

    到这里基本的添加都做好啦,但是标示的移动以及标题超出页面后需要滑动对应每个标题还没做,先来看看滑动怎么做,这里就需要使用ViewPager滑动监听接口中的onPagerScrolled方法,方法中的参数为:
    position:滑动时,屏幕左侧显示的第一个page
    positionOffset:滑动比例,值的范围为[0, 1),手指往左滑动,该值递增,反之递减
    positionOffsetPixels:滑动距离,和屏幕有关,手指往左滑动,该值递增,反之递减

    我们将当前position 以及 position 乘以每个标题的宽度传入scrollToChild方法中,再拿到当前position标题的左边X轴位置 + Positionoffset ,加上if语句如果当前的 position大于0或者 Positionoffset 大于0
    就 -= 我们设置的偏移量 再加上if语句 如果当前 newPositionOffset 不等于lastScrollX 就将 lastScrollX = newPositionOffset;
    scrollTo方法进行移动。

    “注:该方法scrollTo(x,y)正数往左滑动,负数往右滑动与我们所认识的滑动是发向的 详情请看API”

        
        class PageListener implements OnPageChangeListener{
        
            @Override
            public void onPageScrollStateChanged(int state) {
                if(onTabPageChangeListener != null){
                    onTabPageChangeListener.onPageScrollStateChanged(state);
                }   
            }
            @Override
            public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
                SlidingTab.this.currentPosition = position;
                SlidingTab.this.currentPositionOffset = positionOffset;
                scrollToChild(position, (int) (positionOffset * mLayout.getChildAt(position).getWidth()) );
                invalidate();
                if(onTabPageChangeListener != null){
                    onTabPageChangeListener.onPageScrolled(position, positionOffset, positionOffsetPixels);
                }
            }
             
            @Override
            public void onPageSelected(int position) {
    
                if(onTabPageChangeListener != null){
                    onTabPageChangeListener.onPageSelected(position );
                }
            
            }
    
    
        
        void scrollToChild(int position , int positionOffset){
            if(count < 0 ){
                return;
            }
            int newPositionOffset  = mLayout.getChildAt(position).getLeft() + positionOffset;
            if(position > 0 || positionOffset > 0 ){
                newPositionOffset -= offset;
            }
            if(newPositionOffset != lastScrollX){
                lastScrollX = newPositionOffset;
                scrollTo(newPositionOffset, 0);
            }
                    
        }
    

    还有最后一个方法画标示线,获取测量后的View的高度,根据currentPosition 获取标题左左边以及右边左边。
    判断当前currentPositionOffset 是否大于0并且当前currentPosition 小于标题个数,通过条件 根据currentPosition+1 获取标题左左边以及右边左边做下计算:

    
    偏移值  * 第二个标题左边X坐标  + (1.0 - 偏移值)  *   第一个标题的左边X坐标;
    ViewPager滑动过程会不停调用
    onPageScrolled(int position, float positionOffset, int positionOffsetPixels)
     方法 偏移值就会不停的变 通过invalidate();
     去更新图层 
    

    ,将数据画在距形上就搞定啦。

        @Override
        protected void onDraw(Canvas canvas) {
            super.onDraw(canvas);   
            if(count < 0){
                return;
            }
             mPaint.setColor(lineColor);
             int height = getMeasuredHeight();
             float lineLeft = mLayout.getChildAt(currentPosition).getLeft() + textPadding;
             float lineRight = mLayout.getChildAt(currentPosition).getRight()- textPadding;
            if(currentPositionOffset > 0F && currentPosition < count){
                 float Left = mLayout.getChildAt(currentPosition + 1).getLeft()  +  textPadding;
                 float Right = mLayout.getChildAt(currentPosition + 1).getRight()-  textPadding;
                 lineLeft = (currentPositionOffset * Left + (1F - currentPositionOffset) * lineLeft);
                 lineRight = (currentPositionOffset * Right + (1F - currentPositionOffset) * lineRight);
            }
            canvas.drawRect(lineLeft , height - lineHeight, lineRight, height, mPaint);
        }
    
    截图.jpg 最终效果图.gif YHPKD~TKEX26L7CA1%8Z12E.jpg

    如果看不太明白请原谅,第二次写博客不是表达的很好,
    需要源码 留下邮箱~

    相关文章

      网友评论

      • 74646969fbbf:求源码,邮箱401829206@qq.com 多谢
      • 0f421b74ad9f:求源码,邮箱422742446@qq.com
      • 夏顺汉:求源码,邮箱2224344971@qq.com
      • 朝花夕拾不起来:这个官方文档不是有例子么?
        qingwenLi:@朝花夕拾不起来 不一样啦,这个我自己写的
      • 洋Coder:onDraw方法中的if(count<0)应该为if(count<=0)吧?
        qingwenLi:@洋Coder 最近又开始忙,没时间写啦。
        洋Coder:@A其实我很傻 刚好项目要用,又懒得写,直接拿来用了
        qingwenLi:@洋Coder 哈哈,<=0比较严谨些
      • Believe_you:默默的关注你

      本文标题:模仿PagerSlidingTabStrip - ViewPag

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