模仿PagerSlidingTabStrip - ViewPager滑动视图

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

自从上班后一直以来项目用到比较有难度的控件都上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

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,064评论 5 466
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,606评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,011评论 0 328
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,550评论 1 269
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,465评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 47,919评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,428评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,075评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,208评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,185评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,191评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,914评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,482评论 3 302
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,585评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,825评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,194评论 2 344
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,703评论 2 339

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,228评论 25 707
  • 《裕语言》速成开发手册3.0 官方用户交流:iApp开发交流(1) 239547050iApp开发交流(2) 10...
    叶染柒丶阅读 25,592评论 5 18
  • 《ilua》速成开发手册3.0 官方用户交流:iApp开发交流(1) 239547050iApp开发交流(2) 1...
    叶染柒丶阅读 10,410评论 0 11
  • 如果可以,我反而希望我从没来过。 如果不是第三个村庄里没有小卖部,恐怕这里会让人误以为这里就是鳄鱼山庄。因为布局几...
    Yoyo月丫弯弯阅读 235评论 1 1
  • 月到中秋分外明,晴朗的夜,明亮的月光映照着大地,围着一群人,有小孩有老人,桌子上放着月饼,放着零食,人们嘻嘻...
    微若尘阅读 195评论 0 0