天天看點

Android自定義ViewPager圖檔訓示器,相容實作底部橫線訓示器

前言

 記得以前自己使用過的ViewPager Indicator有JakeWharton大神的開源庫ViewPagerIndicator,v4包自帶的PagerTitleStrip以及Android Support Design庫的TabLayout。它們基本上可以實作項目中常見的ViewPager訓示器的需求,除非你的項目有特色的訓示器需求,如訓示器不再是tab底部橫線,而是一個三角形或是其他形狀,亦或是圖檔。這個時候你可能就需要自己去擴充以上或是去github上search看看有沒有現成符合你項目需求的ViewPager Indicator訓示器。不過話說回來,以上提到Jake的ViewPagerIndicator,官方的PagerTitleStrip,TabLayout對于訓示器擴充來講并不能符合你項目的需求,這個時候,由于項目需求,我們不得不自己定義ViewPager訓示器。這篇文章将會教會大家自己動手實作一個ViewPager圖檔訓示器。先看看将要實作效果圖:

Android自定義ViewPager圖檔訓示器,相容實作底部橫線訓示器
Android自定義ViewPager圖檔訓示器,相容實作底部橫線訓示器
Android自定義ViewPager圖檔訓示器,相容實作底部橫線訓示器
Android自定義ViewPager圖檔訓示器,相容實作底部橫線訓示器

可以看到我們将要實作的效果不僅僅支援圖檔訓示器也支援以前的底部橫線訓示器,下面我們将動手實作以上的效果。

如何實作?

 在自定義ViewPagerIndicator前我們首先需要思考如何才能實作以上的效果,我們不妨先觀察觀察這個訓示器是如何構造的,在這裡我給出的答案是其實它就是一個LinearLayout(容器)外加底部繪制的一個圖形(圖檔,顔色或是形狀),并在ViewPager的移動過程中改變底部圖形及Indicator容器的位置。知道了這個訓示器的組成結構後我們就可以開始動手寫代碼實作它了。

實作

1. 繼承LinearLayout并設定好成員變量及自定義屬性

/**
 * Created by lt on 2018/4/20.
 */
public class ImagePagerIndicator extends LinearLayout {

    private static final String TAG = "ImagePagerIndicator";
    /**
     * 訓示器圖檔和容器高度比率最大值(為了更好的适配,防止圖檔過大)
     */
    public static final float DEFAULT_IMAGE_HEIGHT_RADIO = /F;
    /**
     * 預設可見tab的數量
     */
    public static final int DEFAULT_TAB_VISABLE_COUNT = ;
    /**
     * 選中與沒有選中的顔色
     */
    private int mHigh_light_color;
    private int mNumal_color;
    /**
     * 訓示器的寬高
     */
    private int mIndicatorHeight;
    private int mIndicatorWidth;
    /**
     * tab标題的寬度
     */
    private int mTabWidth;
    private Paint mPaint;
    /**
     * 訓示器初始時的偏移量
     */
    private int mStartTranslateX;
    /**
     * 訓示器跟随移動的偏移量
     */
    private int mTranslateX ;
    /**
     * 可見tab标題的數量
     */
    private int mTabVisiableCount = DEFAULT_TAB_VISABLE_COUNT;
    private ViewPager mViewPager;
    private ViewPager.OnPageChangeListener mOnPageChangeListener;

    /**
     * 訓示器圖檔
     */
    private Bitmap mImageBitmap;
    /**
     * 指定圖檔訓示器和容器的最大高度比率
     */
    private float mImageHeightRadio;


    public ImagePagerIndicator(Context context) {
        this(context,null);
    }

    public ImagePagerIndicator(Context context, AttributeSet attrs) {
        this(context, attrs,);
    }

    public ImagePagerIndicator(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        // 初始化畫筆
        mPaint = new Paint();
        mPaint.setAntiAlias(true);
        mPaint.setPathEffect(new CornerPathEffect()); // 設定畫筆平滑圓角,不然看起來尖銳
        mPaint.setStyle(Paint.Style.FILL);
        mPaint.setColor(Color.BLUE);
        // 擷取自定義屬性
        TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.ImagePagerIndicator);
        mHigh_light_color = typedArray.getColor(R.styleable.ImagePagerIndicator_tab_select_color, Color.RED);
        mNumal_color = typedArray.getColor(R.styleable.ImagePagerIndicator_tab_unselect_color, Color.GRAY);
        mTabVisiableCount = typedArray.getInteger(R.styleable.ImagePagerIndicator_tab_visiable_count, DEFAULT_TAB_VISABLE_COUNT);
        mImageHeightRadio = typedArray.getFloat(R.styleable.ImagePagerIndicator_image_radio_height, DEFAULT_IMAGE_HEIGHT_RADIO);
        mIndicatorHeight = (int) typedArray.getDimension(R.styleable.ImagePagerIndicator_indicator_height,dp2px());
        if(mTabVisiableCount <){
            mTabVisiableCount = ; // 指定最小可見數量為1
        }
        Drawable drawable = typedArray.getDrawable(R.styleable.ImagePagerIndicator_tab_indicator);
        // Drawable轉Bitmap
        if(drawable instanceof BitmapDrawable) {
            mImageBitmap = ((BitmapDrawable)drawable).getBitmap();
        }else if(drawable instanceof ColorDrawable){
            mImageBitmap = Bitmap.createBitmap(,mIndicatorHeight,
                    Bitmap.Config.ARGB_8888);
            mImageBitmap.eraseColor(((ColorDrawable) drawable).getColor());//填充顔色
        }
        typedArray.recycle();
    }
           

在構造方法中我們取得我們自定義的屬性,如下:

attrs.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <attr name="tab_select_color" format="color|reference"></attr>
    <attr name="tab_unselect_color" format="color|reference"></attr>
    <attr name="tab_indicator" format="reference|color"></attr>
    <attr name="tab_visiable_count" format="integer"></attr>
    <attr name="image_radio_height" format="float"></attr>
    <attr name="indicator_height" format="dimension"></attr>

    <declare-styleable name="ImagePagerIndicator">
        <attr name="tab_select_color"></attr>
        <attr name="tab_unselect_color"></attr>
        <attr name="tab_indicator"></attr>
        <attr name="tab_visiable_count"></attr>
        <attr name="image_radio_height"></attr>
        <attr name="indicator_height"></attr>
    </declare-styleable>
</resources>
           

在構造方法中我們在擷取圖檔(Bitmap)的時候需要判斷使用者設定的是圖檔還是顔色,是以有如下代碼:

Drawable drawable = typedArray.getDrawable(R.styleable.ImagePagerIndicator_tab_indicator);
        // Drawable轉Bitmap
        if(drawable instanceof BitmapDrawable) {
            mImageBitmap = ((BitmapDrawable)drawable).getBitmap();
        }else if(drawable instanceof ColorDrawable){
            mImageBitmap = Bitmap.createBitmap(,mIndicatorHeight,
                    Bitmap.Config.ARGB_8888);
            mImageBitmap.eraseColor(((ColorDrawable) drawable).getColor());//填充顔色
        }
           

這裡你可能會說不嚴謹,如果使用者沒設定tab_indicator屬性呢,mImageBitmap豈不是為null?當然你可以設定預設為一個color顔色(一樣轉Bitmap),也就是常見的那種底部橫線的訓示器,無妨。

2. 在onSizeChanged()方法中擷取tab及訓示器的寬高

 在構造方法中是擷取不到我們需要的寬高的(為0),是以我們需要在onSizeChanged()方法中擷取,代碼如下:

/**
     * 每次view的尺寸發生變化時都會回調
     * @param w
     * @param h
     * @param oldw
     * @param oldh
     */
    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        Log.i(TAG,"onSizeChanged:w"+w+"h"+h+"oldw"+oldw+"oldh"+oldh);
        mTabWidth = w/mTabVisiableCount;
        mIndicatorHeight = mImageBitmap.getHeight();
        mIndicatorWidth = mImageBitmap.getWidth();
        if(mIndicatorWidth > mTabWidth || mIndicatorWidth == ){
            mIndicatorWidth = mTabWidth;
        }
        int maxIndicatorHeight = (int) (h* mImageHeightRadio);
        if(mIndicatorHeight > maxIndicatorHeight){
            mIndicatorHeight = maxIndicatorHeight;
        }
        Log.i(TAG,"mIndicatorHeight"+mIndicatorHeight);
        mStartTranslateX = mTabWidth/ - mIndicatorWidth/;
        changeTabWidth();
    }

    /**
     * 代碼改變tab标題的布局寬度,防止布局中為不同的title設定不同的寬度
     */
    private void changeTabWidth() {
        int childCount = getChildCount();
        if(childCount == ){
            return;
        }
        for(int i=;i<childCount;i++){
            View child = getChildAt(i);
            LayoutParams layoutParams = (LayoutParams) child.getLayoutParams();
            layoutParams.weight = ;
            layoutParams.width = getWidth()/mTabVisiableCount;
            child.setLayoutParams(layoutParams);
        }
    }
           

這裡你需要了解的是初始偏移量mStartTranslateX的計算,初始偏移量該為tab的寬度二分一減去訓示器寬度的二分之一,這樣可以使得我們的訓示器與标題橫線居中對齊。

3.dispatchDraw()中方法中繪制訓示器圖形

@Override
protected void dispatchDraw(Canvas canvas) {
    super.dispatchDraw(canvas);
    canvas.save();
    // 平移畫布
    canvas.translate(mStartTranslateX +mTranslateX,getHeight()-mIndicatorHeight);
    // 設定圖檔的裁剪區域,為null則不裁剪
    Rect src = new Rect(,,mIndicatorWidth,mIndicatorHeight);
    // 設定圖檔為畫布中顯示的區域,由于将畫布平移了,這裡和圖檔的裁剪區域一緻
    Rect dest = new Rect(,,mIndicatorWidth,mIndicatorHeight);
    // 繪制圖檔
    canvas.drawBitmap(mImageBitmap,src,dest,mPaint);
    canvas.restore();
}
           

注意是dispatchDraw而不是onDraw()。

4.實作訓示器,容器和ViewPager的關聯

完成了以上步驟後僅僅是在第一個title下面顯示了我們想要的圖形訓示器,當ViewPager滑動的時候你會看到我們的圖形訓示器并不會跟随ViewPager關聯,是以我們需要實作訓示器和ViewPager的關聯,代碼如下:

/**
     * 實作訓示器和布局容器的關聯
     * @param position
     * @param offset
     */
    private void scroll(int position, float offset) {
        // 實作訓示器的滾動
        mTranslateX = (int) (mTabWidth*(offset+position));
        invalidate();
        // 實作容器關聯
        Log.i(TAG,position+"%"+mTabVisiableCount+":"+position%mTabVisiableCount);
        // 什麼時候容器需要滾動?
        if(offset >  && getChildCount() > mTabVisiableCount && position > (mTabVisiableCount -)){
            this.scrollTo((position - mTabVisiableCount + ) * mTabWidth + (int) (offset * mTabWidth), );
        }  
    }
           

這個方法在ViewPager傳入合适的position和offset參數後會使得我們的訓示器跟随ViewPager關聯。這裡通過改變mTranslateX 并調用invalidate()讓ViewGroup重新繪制(調用dispatchDraw)實作圖形訓示器的移動,通過容器的scrollTo方法實作容器的關聯。這裡你需要慢慢了解偏移量mTranslateX 的計算和容器什麼時候開始滾動及滾動到的x坐标值(向右滑offset由0.0~1.0變化,向左滑offset有1.0~0.0變化,注意offset隻會無限逼近1.0)。當然,scroll該由誰來調用呢?總不能是我們自己吧,是以,我們還需要給容器設定ViewPager并通過給ViewPager設定滑動監聽器,在監聽器的onPageScrolled方法調用我們的scroll方法,這個就将我們的訓示器和ViewPager綁定在一起了,ViewPager切換的時候我們的訓示器及容器也會跟着移動。代碼如下:

/**
     * 設定ViewPager,實作綁定
     * @param viewPager
     * @param pos
     */
    public void setViewPager(ViewPager viewPager, final int pos){
        mViewPager = viewPager;
        mViewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
            @Override
            public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
                if(mOnPageChangeListener != null){
                    mOnPageChangeListener.onPageScrolled(position,positionOffset,positionOffsetPixels);
                }
                Log.i("onPageScrolled():","positionOffset:"+positionOffset);
                ImagePagerIndicator.this.scroll(position,positionOffset);
            }

            @Override
            public void onPageSelected(int position) {
                if(mOnPageChangeListener != null){
                    mOnPageChangeListener.onPageSelected(position);
                }
                resetTextColor();
                highlightTextColor(position);
            }

            @Override
            public void onPageScrollStateChanged(int state) {
                if(mOnPageChangeListener != null){
                    mOnPageChangeListener.onPageScrollStateChanged(state);
                }
            }
        });
        mViewPager.setCurrentItem(pos);
        resetTextColor();
        highlightTextColor(pos);
        setTabClickListener();
    }
           

這個方法需要注意的是我們将ViewPager設定給了我們的訓示器容器并在裡面給ViewPager設定了OnPageChangeListener并在onPageScrolled方法中調用了我們的scroll方法。同時,這裡使用了addOnPageChangeListener方法而不是過時的setOnPageChangeListener,兩者的差別是前者設定的監聽器隻是ViewPager衆多中的一個,而後者則是覆寫之前設定的監聽器(隻有一個)。

5. 設定tab标題的點選事件

聰明的你可能已經發現上面的setTabClickListener()方法了,那這個方法我們來看看:

/**
     * 設定tab的點選事件
     */
    private void setTabClickListener(){
        for(int i=;i<getChildCount();i++){
            final int j = i;
            getChildAt(i).setOnClickListener(new OnClickListener() {
                @Override
                public void onClick(View view) {
                    mViewPager.setCurrentItem(j);
                }
            });
        }
    }
           

想必,你已經知道該方法的作用了,它實作的是當我們點選我們的tab标題的時候ViewPager也能跟着切換頁面(再度綁定)。

6. 其他方法

/**
     * 重置文本顔色
     */
    private void resetTextColor(){
        for(int i=;i<getChildCount();i++){
            View view = getChildAt(i);
            if(view instanceof TextView){
                ((TextView) view).setTextColor(mNumal_color);
            }
        }
    }

    /**
     * 高亮文本
     * @param pos
     */
    private void highlightTextColor(int pos){
        View view = getChildAt(pos);
        if(view instanceof TextView){
            ((TextView) view).setTextColor(mHigh_light_color);
        }
    }

    public void setOnPageChangeListener(ViewPager.OnPageChangeListener onPageChangeListener) {
            mOnPageChangeListener = onPageChangeListener;
        }

    /**
     * dp轉 px.
     * @param value the value
     * @return the int
     */
    public int dp2px(float value) {
        final float scale = getContext().getResources().getDisplayMetrics().densityDpi;
        return (int) (value * (scale / ) + f);
    }
           

ok,到此為止我們已經實作了這個ImagePagerIndicator了,那我們要如何使用呢?

使用

先看看我們的布局檔案

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.lt.viewpagerindicator.MainActivity">

    <com.lt.viewpagerindicator.ImagePagerIndicator
        android:id="@+id/image_indicator"
        android:background="#131933"
        app:tab_indicator="@mipmap/ic_gear_wheel"
        app:tab_select_color="@color/tab_selected_color"
        app:tab_unselect_color="@color/tab_unselected_color"
        app:tab_visiable_count="4"
        android:layout_width="match_parent"
        android:layout_height="60dp">
    </com.lt.viewpagerindicator.ImagePagerIndicator>

    <android.support.v4.view.ViewPager
        android:id="@+id/view_pager"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
    </android.support.v4.view.ViewPager>

</LinearLayout>

           
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.lt.viewpagerindicator.MainActivity">

    <com.lt.viewpagerindicator.ImagePagerIndicator
        android:id="@+id/image_indicator"
        android:background="#131933"
        app:tab_indicator="@android:color/holo_red_light"
        app:tab_select_color="@color/tab_selected_color"
        app:tab_unselect_color="@color/tab_unselected_color"
        app:tab_visiable_count="3"
        app:indicator_height="6dp"
        android:layout_width="match_parent"
        android:layout_height="60dp">
    </com.lt.viewpagerindicator.ImagePagerIndicator>

    <android.support.v4.view.ViewPager
        android:id="@+id/view_pager"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
    </android.support.v4.view.ViewPager>

</LinearLayout>
           

前者是設定一張圖檔為我們的訓示器後者是設定一個顔色為我們的訓示器。

初始化Indicator及ViewPager

private static final String TAG = "MainActivity";
    private String[] mTitle = {"首頁","資源","視訊","文章"};

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        setContentView(R.layout.activity_main);
        ImagePagerIndicator imagePagerIndicator = (ImagePagerIndicator) findViewById(R.id.image_indicator);
        ViewPager viewPager = (ViewPager) findViewById(R.id.view_pager);
        viewPager.setAdapter(mPagerAdapter);
        imagePagerIndicator.setTabTitles(mTitle,);
        imagePagerIndicator.setViewPager(viewPager,);
        imagePagerIndicator.setOnPageChangeListener(this);
    }

    PagerAdapter mPagerAdapter = new PagerAdapter() {
        @Override
        public int getCount() {
            return mTitle.length;
        }

        @Override
        public boolean isViewFromObject(View view, Object object) {
            return view == object;
        }

        @Override
        public Object instantiateItem(ViewGroup container, int position) {
            TextView textView = new TextView(MainActivity.this);
            textView.setText(mTitle[position]);
            textView.setTextSize(TypedValue.COMPLEX_UNIT_SP,);
            textView.setGravity(Gravity.CENTER);
            container.addView(textView);
            return textView;
        }

        @Override
        public void destroyItem(ViewGroup container, int position, Object object) {
            container.removeView((View) object);
        }
    };
           

自己寫的控件使用的話自然很熟悉啦,關鍵代碼一行imagePagerIndicator.setViewPager(viewPager,0);

想必你可能會需要ImagePagerIndicator的完整代碼:

package com.lt.viewpagerindicator;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.CornerPathEffect;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.support.v4.view.ViewPager;
import android.util.AttributeSet;
import android.util.Log;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import android.widget.TextView;


/**
 * Created by lt on 2018/4/20.
 */
public class ImagePagerIndicator extends LinearLayout {

    private static final String TAG = "ImagePagerIndicator";
    /**
     * 訓示器圖檔和容器高度比率最大值(為了更好的适配,防止圖檔過大)
     */
    public static final float DEFAULT_IMAGE_HEIGHT_RADIO = /F;
    /**
     * 預設可見tab的數量
     */
    public static final int DEFAULT_TAB_VISABLE_COUNT = ;
    /**
     * 選中與沒有選中的顔色
     */
    private int mHigh_light_color;
    private int mNumal_color;
    /**
     * 訓示器的寬高
     */
    private int mIndicatorHeight;
    private int mIndicatorWidth;
    /**
     * tab标題的寬度
     */
    private int mTabWidth;
    private Paint mPaint;
    /**
     * 訓示器初始時的偏移量
     */
    private int mStartTranslateX;
    /**
     * 訓示器跟随移動的偏移量
     */
    private int mTranslateX ;
    /**
     * 可見tab标題的數量
     */
    private int mTabVisiableCount = DEFAULT_TAB_VISABLE_COUNT;
    private ViewPager mViewPager;
    private ViewPager.OnPageChangeListener mOnPageChangeListener;

    /**
     * 訓示器圖檔
     */
    private Bitmap mImageBitmap;
    /**
     * 指定圖檔訓示器和容器的最大高度比率
     */
    private float mImageHeightRadio;


    public ImagePagerIndicator(Context context) {
        this(context,null);
    }

    public ImagePagerIndicator(Context context, AttributeSet attrs) {
        this(context, attrs,);
    }

    public ImagePagerIndicator(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        // 初始化畫筆
        mPaint = new Paint();
        mPaint.setAntiAlias(true);
        mPaint.setPathEffect(new CornerPathEffect()); // 設定畫筆平滑圓角,不然看起來尖銳
        mPaint.setStyle(Paint.Style.FILL);
        mPaint.setColor(Color.BLUE);
        // 擷取自定義屬性
        TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.ImagePagerIndicator);
        mHigh_light_color = typedArray.getColor(R.styleable.ImagePagerIndicator_tab_select_color, Color.RED);
        mNumal_color = typedArray.getColor(R.styleable.ImagePagerIndicator_tab_unselect_color, Color.GRAY);
        mTabVisiableCount = typedArray.getInteger(R.styleable.ImagePagerIndicator_tab_visiable_count, DEFAULT_TAB_VISABLE_COUNT);
        mImageHeightRadio = typedArray.getFloat(R.styleable.ImagePagerIndicator_image_radio_height, DEFAULT_IMAGE_HEIGHT_RADIO);
        mIndicatorHeight = (int) typedArray.getDimension(R.styleable.ImagePagerIndicator_indicator_height,dp2px());
        if(mTabVisiableCount <){
            mTabVisiableCount = ; // 指定最小可見數量為1
        }
        Drawable drawable = typedArray.getDrawable(R.styleable.ImagePagerIndicator_tab_indicator);
        // Drawable轉Bitmap
        if(drawable instanceof BitmapDrawable) {
            mImageBitmap = ((BitmapDrawable)drawable).getBitmap();
        }else if(drawable instanceof ColorDrawable){
            mImageBitmap = Bitmap.createBitmap(,mIndicatorHeight,
                    Bitmap.Config.ARGB_8888);
            mImageBitmap.eraseColor(((ColorDrawable) drawable).getColor());//填充顔色
        }
        typedArray.recycle();
    }

    /**
     * 每次view的尺寸發生變化時都會回調
     * @param w
     * @param h
     * @param oldw
     * @param oldh
     */
    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        Log.i(TAG,"onSizeChanged:w"+w+"h"+h+"oldw"+oldw+"oldh"+oldh);
        mTabWidth = w/mTabVisiableCount;
        mIndicatorHeight = mImageBitmap.getHeight();
        mIndicatorWidth = mImageBitmap.getWidth();
        if(mIndicatorWidth > mTabWidth || mIndicatorWidth == ){
            mIndicatorWidth = mTabWidth;
        }
        int maxIndicatorHeight = (int) (h* mImageHeightRadio);
        if(mIndicatorHeight > maxIndicatorHeight){
            mIndicatorHeight = maxIndicatorHeight;
        }
        Log.i(TAG,"mIndicatorHeight"+mIndicatorHeight);
        mStartTranslateX = mTabWidth/ - mIndicatorWidth/;
        changeTabWidth();
    }

    /**
     * 代碼設定tab的标題,及文字大小(機關為sp)
     * @param titles
     * @param textSize
     */
    public void setTabTitles(String[] titles,float textSize){
        if(titles == null || titles.length ==){
            return;
        }
        // 動态添加Title,這裡将使得布局設定的tab标題全部移除
        removeAllViews();
        for(String title : titles){
            TextView textView = new TextView(getContext());
            textView.setText(title);
            textView.setGravity(Gravity.CENTER);
            textView.setTextSize(TypedValue.COMPLEX_UNIT_SP,textSize);
            LayoutParams layoutParams = new LayoutParams(, ViewGroup.LayoutParams.MATCH_PARENT);
            layoutParams.weight = ;
            addView(textView,layoutParams);
        }
    }

    /**
     * 代碼改變tab标題的布局寬度,防止布局中為不同的title設定不同的寬度
     */
    private void changeTabWidth() {
        int childCount = getChildCount();
        if(childCount == ){
            return;
        }
        for(int i=;i<childCount;i++){
            View child = getChildAt(i);
            LayoutParams layoutParams = (LayoutParams) child.getLayoutParams();
            layoutParams.weight = ;
            layoutParams.width = getWidth()/mTabVisiableCount;
            child.setLayoutParams(layoutParams);
        }
    }

    /**
     * 設定ViewPager,實作綁定
     * @param viewPager
     * @param pos
     */
    public void setViewPager(ViewPager viewPager, final int pos){
        mViewPager = viewPager;
        mViewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
            @Override
            public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
                if(mOnPageChangeListener != null){
                    mOnPageChangeListener.onPageScrolled(position,positionOffset,positionOffsetPixels);
                }
                Log.i("onPageScrolled():","positionOffset:"+positionOffset);
                ImagePagerIndicator.this.scroll(position,positionOffset);
            }

            @Override
            public void onPageSelected(int position) {
                if(mOnPageChangeListener != null){
                    mOnPageChangeListener.onPageSelected(position);
                }
                resetTextColor();
                highlightTextColor(position);
            }

            @Override
            public void onPageScrollStateChanged(int state) {
                if(mOnPageChangeListener != null){
                    mOnPageChangeListener.onPageScrollStateChanged(state);
                }
            }
        });
        mViewPager.setCurrentItem(pos);
        resetTextColor();
        highlightTextColor(pos);
        setTabClickListener();
    }

    /**
     * 設定tab的點選事件
     */
    private void setTabClickListener(){
        for(int i=;i<getChildCount();i++){
            final int j = i;
            getChildAt(i).setOnClickListener(new OnClickListener() {
                @Override
                public void onClick(View view) {
                    mViewPager.setCurrentItem(j);
                }
            });
        }
    }

    /**
     * 重置文本顔色
     */
    private void resetTextColor(){
        for(int i=;i<getChildCount();i++){
            View view = getChildAt(i);
            if(view instanceof TextView){
                ((TextView) view).setTextColor(mNumal_color);
            }
        }
    }

    /**
     * 高亮文本
     * @param pos
     */
    private void highlightTextColor(int pos){
        View view = getChildAt(pos);
        if(view instanceof TextView){
            ((TextView) view).setTextColor(mHigh_light_color);
        }
    }


    @Override
    protected void dispatchDraw(Canvas canvas) {
        super.dispatchDraw(canvas);
        canvas.save();
        // 平移畫布
        canvas.translate(mStartTranslateX +mTranslateX,getHeight()-mIndicatorHeight);
        // 設定圖檔的裁剪區域,為null則不裁剪
        Rect src = new Rect(,,mIndicatorWidth,mIndicatorHeight);
        // 設定圖檔為畫布中顯示的區域,由于将畫布平移了,這裡和圖檔的裁剪區域一緻
        Rect dest = new Rect(,,mIndicatorWidth,mIndicatorHeight);
        // 繪制圖檔
        canvas.drawBitmap(mImageBitmap,src,dest,mPaint);
        canvas.restore();
    }

    /**
     * 實作訓示器和布局容器的關聯
     * @param position
     * @param offset
     */
    private void scroll(int position, float offset) {
        // 實作訓示器的滾動
        mTranslateX = (int) (mTabWidth*(offset+position));
        invalidate();
        // 實作容器關聯
        Log.i(TAG,position+"%"+mTabVisiableCount+":"+position%mTabVisiableCount);
        // 什麼時候容器需要滾動?
        if(offset >  && getChildCount() > mTabVisiableCount && position > (mTabVisiableCount -)){
            this.scrollTo((position - mTabVisiableCount + ) * mTabWidth + (int) (offset * mTabWidth), );
        }
    }

    public void setOnPageChangeListener(ViewPager.OnPageChangeListener onPageChangeListener) {
        mOnPageChangeListener = onPageChangeListener;
    }

    /**
     * dp轉 px.
     * @param value the value
     * @return the int
     */
    public int dp2px(float value) {
        final float scale = getContext().getResources().getDisplayMetrics().densityDpi;
        return (int) (value * (scale / ) + f);
    }
}
           

也或許你覺得這個小齒輪很可愛,那麼你可能會需要整個項目的github:ImagePagerIndicator

亦或是你想要的是形狀訓示器,那麼很抱歉我這裡沒有,但形狀訓示器隻需你在dispatchDraw方法中繪制你想要的形狀即可,随性所欲,如你想要hongyang大神的三角形訓示器,你可以看看他是如何繪制三叫形的。

參考文章:Android 教你打造炫酷的ViewPagerIndicator 不僅僅是高仿MIUI