1. 程式人生 > >Android從零開搞系列:自定義View(4)基本的自定義ViewPager指示器+開源專案分析(上)

Android從零開搞系列:自定義View(4)基本的自定義ViewPager指示器+開源專案分析(上)

基本的自定義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();
        }
    //此方法會在父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指示器的部分就到此結束了。那麼接下來便是:

    二:開源專案分析

    先看一波效果

    這裡寫圖片描述

    尾聲

    先記錄到此,以後遇到實用的就繼續加上。