Android从零开搞系列:自定义View(4)基本的自定义ViewPager指示器+开源项目分析(上)

转载请注意:http://blog.csdn.net/wjzj000/article/details/53646358
本菜开源的一个自己写的Demo,希望能给Androider们有所帮助,水平有限,见谅见谅…
https://github.com/zhiaixinyang/PersonalCollect (拆解GitHub上的优秀框架于一体,全部拆离不含任何额外的库导入)
https://github.com/zhiaixinyang/MyFirstApp(Retrofit+RxJava+MVP)

基本的自定义ViewPager的指示器


当然关于ViewPager指示器,如果只需要简洁大方,那么我们最简单的方案就是使用TabLayout+ViewPager。
当然咱们也有很多非常不错的开源框架可以选择。
本次的记录的内容就是为了自己对其中涉及的内容进行几率和梳理。

关于本次:基础自定义+开源框架分析

一、基础的自定义部分

这个效果的代码思路来自于鸿洋大神的博客还有他在慕课上的视频。感觉看博客蛋疼菊紧可以去慕课看洋神的视频。


先让我们看一下效果

这里写图片描述

思路梳理

主体分为俩个部分:ViewPager+指示器。ViewPager没啥好说的该咋霍霍咋霍霍。
指示器:需要我们自己去自定义。既然是横向的,那就继承与LinearLayout,然后动态增加里边的内容(Tab)。关于那个可以移动的小三角形,我们将使用Canvas进行绘制,它的移动将和ViewPager进行相关。通过addOnPageChangeListener()监听。

代码分析

先让我们看一看添加动态Tab
//这里仿照TabLayout的写法。设置全部Tab的标题
public void setTitles(List<String> titles){
        //先移除内部的全部子View(虽然现阶段根本啥也没有...)
        removeAllViews();
        //默认排列方式是居中的
        setGravity(Gravity.LEFT);
        if (titles!=null) {
            for (String title : titles) {
                TextView textView = new TextView(MyApplication.getAllContext());
                textView.setText(title);
                LinearLayout.LayoutParams lp=new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
                /**
                 * 注意getScreenWidth是获取屏幕宽度的自定义的方法。
                 * 此处为什么要自己写一个获取屏幕宽度的方式,please往下看。
                 */
                lp.width= (int) (getScreenWidth()/3);
                textView.setTextColor(Color.WHITE);
                textView.setGravity(Gravity.CENTER);
                textView.setTextSize(TypedValue.COMPLEX_UNIT_SP,14);
                addView(textView,lp);
            }
            setTabClickEvent();
        }
    }
    private float getScreenWidth(){
        DisplayMetrics displayMetrics = new DisplayMetrics();
        WindowManager windowManager = (WindowManager) getContext()
                .getSystemService(Context.WINDOW_SERVICE);
        windowManager.getDefaultDisplay().getMetrics(displayMetrics);
        return displayMetrics.widthPixels;
    }

这里解释为什么调用系统的获取屏幕的宽度。


  • 先让我们看一张图
    这里写图片描述

这是个方法的回调顺序。让我们一点点看:
  • 构造方法第一个被调用这个没啥好说咱们跳过….
  • 正式第一个被回调的onFinishInflate():当我们的布局中的XML加载完毕后,这个方法回调。此时通过getWidth()获取的控件的宽度是为0。
  • 第二第三个方法,是我们自己设置的。写在View初始化之后。但是我们要注意!他们先于View的绘制系列方法之前被调用。因此此时通过getWidth()获取的控件的宽度是为0。所以我们要使用系统的方式在此处获取屏幕宽高来动态的给Tab赋值。
    • 紧接着我们可以看到onMeasure()被调用俩次,这里没啥有价值的东西。核心是onSizeChanged()方法别调用后,这里的getWidth()即可得到控件的宽高!然后onMeasure()onLayout()onDarw()被执行。父View完成绘制。
  • 最后一个回调dispatchDraw():此方法用于绘制父控件中的子控件。

    那么为什么要使用系统的方式获取宽高这个问题便有了答案。


  • 接下来让我们看一看画三角形

    这里真的没有什么难的,大家这自信心上一定不要打怵,不难!

    首先是先关的初始操作

            //初始化画笔
            paint=new Paint();
            paint.setColor(Color.WHITE);
            //抗锯齿开启
            paint.setAntiAlias(true);
            //传入的此类,会使画笔的线段拐角有圆角效果
            paint.setPathEffect(new CornerPathEffect(3));
            paint.setStyle(Paint.Style.FILL);
    
        //绘制三角形,就是三条线段(Path)按自己想要的长度围成的闭合图形,然后通过Canvas.drawPath完成绘制。
        //此方法在每次View尺寸发生变化时回调。因此在此处我们可以拿到这个控件的宽和高
        @Override
        protected void onSizeChanged(int w, int h, int oldw, int oldh) {
            super.onSizeChanged(w, h, oldw, oldh);
            triangleWidth=w/18;//三角形的宽(设置为屏幕宽度的十八分之一)
            //三角形的高,此处我直接用的宽的一半。也就是说我们绘制的是一个直角三角形
            triangleHeight=triangleWidth/2;
            //开始时三角形的初始偏移量。(就是第一个Tab宽度的一半然后减去半个三角形的宽。)
            initLenght=w/6-triangleHeight/2;
            width=w;
            height=h;
            //通过Path路径来画三角形
            path=new Path();
            //首先移动到(0,0)
            path.moveTo(0,0);
            //然后从(0,0)画到(三角形宽度,0)这个点
            path.lineTo(triangleWidth,0);
            /**
             *然后从(triangleWidth,0)画到三角形的定点(triangleWidth/2,-triangleHeight)
             * 因为我们的(0,0)是起点,所以顶点的y在(0,0)之上,根据安卓的特性所以顶点y为负
             */
            path.lineTo(triangleWidth/2,-triangleHeight);
            //闭合路径
            path.close();
        }

    此时绘制三角形的画笔和路径都已经存在,接下来就是Canvas绘制了!
    如果对Canvas类不是很了解,可以参考我的另一篇博客:
    http://blog.csdn.net/wjzj000/article/details/53589024


    //此方法会在父View画子View的时候回调,因此我们的三角形在此处绘制.此方法会被循环回调
        @Override
        protected void dispatchDraw(Canvas canvas) {
            canvas.save();
            //移动画布,我们的三角形要随ViewPager的滑动而滑动,
            /**
             * 这里的思路:
             * 我们绘制三角形的坐标不变,因此我们的画布要移动。怎么移动?移动多少?
             * 很明显,三角形的移动和ViewPager的移动有关联。
             * 而且这个移动值是一个动态变化的值。而通过addOnPageChangeListener()监听中的特定回调方法即可完成这个效果。offsetLenght这个变量就是接受ViewPager监听回调的。(初始为0)
             * /
            canvas.translate(initLenght+offsetLenght,getHeight());
            canvas.drawPath(path,paint);
            canvas.restore();
            super.dispatchDraw(canvas);
        }

    OK,这样一个静态三角形就画好。上文中代码提到,想要三角形动,那么就要监听ViewPager的滑动,那么接下来让我们看一看ViewPager的监听。


    //代码比较简单,而且注释已包含其中。
    public void setViewPager(ViewPager viewPager){
            this.viewPager=viewPager;
            viewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
                @Override
                public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
                    /**
                     * position:ViewPager中Fragment的位置。本例则为:0,1,2
                     * positionOffset:简单打印一下就会发现这个值。随着滑动的完成,数值的变化时0-1。
                     */
                    //我们在使用Canvas绘制三角形所用到的offsetLenght就是在这个方法中别动态赋值
                    setOffsetLength(position,positionOffset);
                    setSelectTab(position);
                }
    
                @Override
                public void onPageSelected(int position) {
                    //当我们选择特定的ViewPager内部对象时返回这个对象的位置。
                }
    
                @Override
                public void onPageScrollStateChanged(int state) {
                    /**
                     * 基本上的滑动回调的状态都是这三种:
                     * SCROLL_STATE_IDLE 当前未滚动(滚动停止后调用)
                     * SCROLL_STATE_DRAGGING 当前正在被外部输入(如用户触摸输入)拖动(正在滑动,且手指不离开屏幕)
                     * SCROLL_STATE_SETTLING 目前正在动画到最终位置,而不在外部控制之下(惯性滑动)
                     */
                }
            });
            //设置默认Viewpager显示的页面
            viewPager.setCurrentItem(0);
            setSelectTab(0);
        }
    public void setOffsetLength(int position,float offsetLenght){
            this.offsetLenght = (int) ((getWidth() / 3) * (offsetLenght + position));
            if (position>=1){
                //导航条的移动
                //此方法用于View(也就是Tab)的滑动。
                scrollTo((int) ((position-1)*getWidth()/3+getWidth()/3*offsetLenght),0);
            }
            //此方法作用在UI线程使自身重新绘制。重新调用draw()。
            invalidate();
        }
    

    这样之后,我们三角形就可以跟随ViewPager的移动而移动。当然我们的Tab也可以随着滑动。核心就是上边那个方法中的scrollTo()。
    到这我们基本上已经完成了大部分,接下啦就是Tab被选中的高亮效果和点击效果。


        //这里真心没有啥好解释的- -!
        private void setSelectTab(int position){
            resetTabColor();
            if (getChildAt(position) instanceof TextView){
                ((TextView) getChildAt(position)).setTextColor(Color.BLUE);
                ((TextView) 
                //这种方式可以直接设置18sp
                getChildAt(position)).setTextSize(TypedValue.COMPLEX_UNIT_SP,18);
            }
        }
    
        private void resetTabColor() {
            for (int i = 0; i < getChildCount(); i++) {
                if (getChildAt(i) instanceof TextView){
                    ((TextView) getChildAt(i)).setTextColor(Color.WHITE);
                    ((TextView) getChildAt(i)).setTextSize(TypedValue.COMPLEX_UNIT_SP,18);
                }
            }
        }
        //此方法应该在Tab被初始化的时候调用。
        private void setTabClickEvent(){
            resetTabColor();
            for (int i=0;i<getChildCount();i++){
                final int a=i;
                final View view=getChildAt(i);
                if (view instanceof  TextView){
                    view.setOnClickListener(new OnClickListener() {
                        @Override
                        public void onClick(View v) {
                            viewPager.setCurrentItem(a);
                        }
                    });
                }
            }
        }
    Ok,关于基本的自定义ViewPager指示器的部分就到此结束了。那么接下来便是:

    二:开源项目分析

    先看一波效果

    这里写图片描述

    此项目来自于:
    https://github.com/hackware1993/MagicIndicator
    篇幅过长,所以开源项目分析请移步:
    http://blog.csdn.net/wjzj000/article/details/53664920


    尾声

    先记录到此,以后遇到实用的就继续加上。

    最后希望各位看官可以star我的GitHub,三叩九拜,满地打滚求star:
    https://github.com/zhiaixinyang/PersonalCollect
    https://github.com/zhiaixinyang/MyFirstApp

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值