android 自定義Indicator

/**
 * @author on 2018/11/12.
 * Describe: 自定義指示器
 *               1.支持指示器與文字等長效果 ******;
 *               2.支持指示器與指示器文本等長
 *               3.支持選中調整文字的大小
 *               4.鏈式調用 支持各種顏色的修改
 *               5.支持三角指示器以及下劃線指示器效果
 *               和TabLayout效果相類似
 * 注意點:
 * 確保一定在ViewPagerAdapter中返回了:標題字符串,否則 <addTextFromViewPager/> 會報空指針異常
 */
public class NiceViewPagerIndicator extends HorizontalScrollView {

    public  enum IndicatorType{
        /**
         * 與標題等長
         */
        EQUAL_TAB,
        /**
         * 與文字等長
         */
        EQUAL_TEXT,
        /**
         * 指示器絕對長度
         */
        ABSOLUTE_LENGTH;
    }

    public enum IndicatorShape{
        /**
         * 線性
         */
        LINEAR,
        /**
         * 三角
         */
        TRIANGLE
    }

    /**
     * 記錄當前指示器的左右的X軸座標
     */
    private float mLineLeft;
    private float mLineRight;

    private  NicePageChangeListener mNicePageChangeListener;
    private ViewPager mViewPager;
    private Context mContext;
    private IndicatorType mIndicatorType = IndicatorType.EQUAL_TEXT;
    private IndicatorShape mIndicatorShape = IndicatorShape.LINEAR;

    /**
     * 控件的高度
     */
    private int mIndicatorHeight;
    private int mIndicatorWidth;

    /**
     * 三角指示器------
     */
    private Path mTrianglePath;
    private int mTriangleWidth = 14;
    private int mTriangleHeight = 6;
    private Paint mTrianglePaint;



    /**
     * ScrollView下的唯一子佈局
     * 承載容納文本控件的作用 -----
     */
    private LinearLayout mLinearContainer;
    /**
     * 滑動過程中的選中操作指示器Index
     */
    private int mSelectedIndex;
    /**
     * 滑動結束後停留的指示器Index
     */
    private int mCurrentIndex;
    /**
     * ViewPager 滑動參數 0-1
     */
    private float mCurrentPositionOffset;
    /**
     * 標示tab的個數
     */
    private int mTabCount;

    /**
     * 默認控制器水平padding
     */
    private static final int DEFAULT_TAB_HNL_PADDING = 20;
    /**
     * 水平方向上的文本控件Padding
     */
    private int mTabHorizontalPadding = DEFAULT_TAB_HNL_PADDING;

    /**
     *  根據控件的多少設定是否需要等分:
     *  false ----- 設置標題平分(針對標題較 少)  [標題不可滑動]
     *  true  ----- 設置標題按一定的Padding鋪滿整個Linear 適應較多的標題 [標題可能可以滑動]
     */

    private boolean isExpand = true;
    private LinearLayout.LayoutParams wrapTabLayoutParams;
    private LinearLayout.LayoutParams expandTabLayoutParams;
    /*
     * 指示器(被選中的tab下的短橫線)
     * true:indicator與文字等長;false:indicator與整個tab等長
     */

    private int mNormalTextColor = Color.BLACK;
    private int mSelectedTextColor = Color.RED;
    private int mNormalTextSize = 30;
    private int mSelectedTextSize = 35;
    /**
     * 標識 HorizontalScrollView滑動的x點
     */
    private int lastScrollX = 0;

    /**
     * 用於測量文字長度的畫筆
     */
    private Paint mMeasureTextPaint;

    private Paint mIndicatorPaint;
    private int mIndicatorColor = Color.GREEN;

    private int mIndicatorStrokeWidth = 10;

    /**
     * 在ABSOLUTE_LENGTH 指示器類型下,默認的指示器長度
     */
    private int mIndicatorLength = 40;

    /*
     * scrollView整體滾動的偏移量,dp
     */
    private int mScrollOffset = 100;


    /*----------------- 使用者可根據需要進行參數的修正 契合自己項目的需求-------------------*/

    /**
     * 設置指示器的高度
     * @param indicatorHeightDp dp爲單位
     * @return
     */
    public NiceViewPagerIndicator setIndicatorHeight(int indicatorHeightDp){
        this.mIndicatorStrokeWidth = indicatorHeightDp;
        return this;
    }

    /**
     * 是否與文字等長 --------
     * @return
     */
    public NiceViewPagerIndicator setIndicatorLengthType(IndicatorType mIndicatorType){
        this.mIndicatorType = mIndicatorType;
        return this;
    }


    public NiceViewPagerIndicator setIndicatorShapeType(IndicatorShape indicatorShapeType){
        this.mIndicatorShape = indicatorShapeType;
        return this;
    }

    /**
     * 設置文本指示器的水品左右水品padding
     */
    public NiceViewPagerIndicator setIndicatorHorlPadding(int tabHorizontalPadding){
        this.mTabHorizontalPadding = tabHorizontalPadding;
        return this;
    }



    /**
     * 設置是否平分佈局
     * @param isExpand boolean
     * @return
     */
    public NiceViewPagerIndicator setIsExpand(boolean isExpand){
        this.isExpand = isExpand;
        return this;
    }

    public NiceViewPagerIndicator setIndicatorColor(int indicatorColor){
        this.mIndicatorColor = indicatorColor;
        return this;
    }

    /**
     * 設置未選中的顏色
     * @param colorID
     * @return
     */
    public NiceViewPagerIndicator setNormalTextColor(int colorID){
        this.mNormalTextColor = colorID;
        return this;
    }

    /**
     * 設置選中的顏色
     * @param colorID
     * @return
     */
    public NiceViewPagerIndicator setSelectedTextColor(int colorID){
        this.mSelectedTextColor = colorID;
        return this;
    }

    /**
     * 設置爲選中的文字的顏色
     * @param textNormalSp
     * @return
     */
    public NiceViewPagerIndicator setNormalTextSize(int textNormalSp){
        this.mNormalTextColor = textNormalSp;
        return this;
    }

    /**
     * 設置已經選中的文字的顏色
     * @param textSelectedSp
     * @return
     */
    public NiceViewPagerIndicator setSelectedTextSize(int textSelectedSp){
        this.mSelectedTextSize = textSelectedSp;
        return this;
    }

    /**
     * 設置指示器的長度
     * @param indicatorLength
     * @return
     */
    public NiceViewPagerIndicator setIndicatorLength(int indicatorLength){
        this.mIndicatorLength = indicatorLength;
        return this;
    }


    /**
     * ----------------------------------構造方法開始---------------------------------
     */


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

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

    public NiceViewPagerIndicator(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        // 初始化ViewPager監聽
        mNicePageChangeListener = new NicePageChangeListener();
        mContext = context;
        setFillViewport(true);
        // 允許ViewGroup執行onDraw()執行繪製操作 ---
        setWillNotDraw(false);
    }

    /**
     * ----------------------------------構造方法結束---------------------------------
     */


    /**
     * 屬性初始化完成了綁定ViewPager 並執行各項初始化操作
     * @param viewPager
     */
    public void setUpViewPager(@NonNull ViewPager viewPager){
        this.mViewPager = viewPager;
        // 在adapter爲空時拋出異常 解決後面報的空指針檢查
        if (mViewPager.getAdapter() == null) {
            throw new IllegalStateException("ViewPager does not have adapter instance.");
        }
        viewPager.addOnPageChangeListener(mNicePageChangeListener);
        init();
        initViews();
    }

    /**
     * 進行初始化
     */
    private void init() {
        float density = getResources().getDisplayMetrics().density;
        mSelectedTextSize = (int)(mSelectedTextSize * density);
        mNormalTextSize = (int)(mNormalTextSize * density);
        mTabHorizontalPadding = (int)(mTabHorizontalPadding * density);
        mScrollOffset = (int)(mScrollOffset * density);
        mIndicatorLength =(int)(mIndicatorLength * density);
        mTriangleHeight = (int)(mTriangleHeight * density);
        mTriangleWidth = (int)(mTriangleWidth * density);
        addOnlyContainerChild();
        defTextIndicatorParams();
        initMeasureTextPaints();
        initIndicatorPaints();
        initTrianglePaint();
    }

    private void initTrianglePaint() {
        mTrianglePaint = new Paint();
        mTrianglePaint.setAntiAlias(true);
        mTrianglePaint.setDither(true);
        mTrianglePaint.setColor(Color.WHITE);
        mTrianglePaint.setStyle(Paint.Style.FILL);
    }

    private void initMeasureTextPaints() {
        /*
         * 文字畫筆
         */
        mMeasureTextPaint = new Paint();
        mMeasureTextPaint.setAntiAlias(true);
        mMeasureTextPaint.setStrokeWidth(mIndicatorStrokeWidth);
        mMeasureTextPaint.setTextSize(mSelectedTextSize);
    }

    private void initIndicatorPaints() {
        mIndicatorPaint = new Paint();
        mIndicatorPaint.setColor(mIndicatorColor);
        mIndicatorPaint.setStrokeCap(Paint.Cap.ROUND);
        mIndicatorPaint.setStrokeWidth(mIndicatorStrokeWidth);
        mIndicatorPaint.setAntiAlias(true);
        mIndicatorPaint.setDither(true);
        mIndicatorPaint.setStyle(Paint.Style.FILL);
    }


    /**
     * 添加Horizontal 唯一子佈局
     */
    private void addOnlyContainerChild() {
        mLinearContainer = new LinearLayout(mContext);
        mLinearContainer.setOrientation(LinearLayout.HORIZONTAL);
        mLinearContainer.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
                ViewGroup.LayoutParams.MATCH_PARENT));
        // 將唯一子佈局添加上去
        addView(mLinearContainer);
    }

    /**
     * 創建兩個Tab的LayoutParams,wrapTabLayoutParams     ---  爲寬度包裹內容,控件較多
     *                          expandTabLayoutParams   ---  寬度等分父控件剩餘空間,控件較少
     */
    private void defTextIndicatorParams() {
        //寬度包裹內容
        wrapTabLayoutParams = new LinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT);
        //寬度等分 這裏用途會更加廣泛一點----
        expandTabLayoutParams = new LinearLayout.LayoutParams(0, LayoutParams.MATCH_PARENT, 1.0f);
    }

    private void initViews() {
        mSelectedIndex = mViewPager.getCurrentItem();
        mCurrentIndex = mViewPager.getCurrentItem();
        if (mViewPager.getAdapter() != null) {
            mTabCount = mViewPager.getAdapter().getCount();
        }
        addTextFromViewPager();

        //滾動scrollView
        getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {
                if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {
                    getViewTreeObserver().removeGlobalOnLayoutListener(this);
                } else {
                    getViewTreeObserver().removeOnGlobalLayoutListener(this);
                }
                // 繪製好了完成滑動------
                scrollToChild(mCurrentIndex, 0);
            }
        });
    }

    /**
     * 將從綁定關係的ViewPager的文本屬性,添加到linear_container佈局中去
     *
     * 跳過編譯條件,滿足下面的條件
     */
    @SuppressWarnings("ConstantConditions")
    private void addTextFromViewPager(){
        mLinearContainer.removeAllViews();
        for (int i=0 ;i< mTabCount ;i++){
            addTextTab(i, mViewPager.getAdapter().getPageTitle(i).toString());
        }
        updateTextTabStyle();
    }

    /**
     * 按照選中的更新標題式樣
     */
    private void updateTextTabStyle() {
        for (int i=0;i<mTabCount;i++){
            TextView textView = (TextView) mLinearContainer.getChildAt(i);
            if ( i == mSelectedIndex){
                // 以px傳遞
                textView.setTextSize(TypedValue.COMPLEX_UNIT_PX,mSelectedTextSize);
                textView.setTextColor(mSelectedTextColor);
            }else {
                textView.setTextSize(TypedValue.COMPLEX_UNIT_PX,mNormalTextSize);
                textView.setTextColor(mNormalTextColor);
            }
        }
    }

    /**
     * 添加文本Tab
     * @param i
     * @param pageTitle
     */
    private void addTextTab(int i, String pageTitle) {
        TextView textTab = new TextView(mContext);
        textTab.setGravity(Gravity.CENTER);
        textTab.setText(pageTitle);
        // 暫時定爲紅色
        textTab.setTextColor(mNormalTextColor);
        textTab.setTextSize(mNormalTextSize);
        // 設置左右的Padding
        textTab.setPadding(mTabHorizontalPadding,0,mTabHorizontalPadding,0);
        textTab.setOnClickListener(new TextClickSelectListener(i));
        // 設置文本控件的佈局params方式
        mLinearContainer.addView(textTab,isExpand?expandTabLayoutParams:wrapTabLayoutParams);
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        mIndicatorHeight = h;
        mIndicatorWidth = w;

    }

    /**
     * 繪製指示器 ----- 計算指示器的起點與中點進行繪製
     * @param canvas
     */
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        if (mTabCount == 0){
            return;
        }

        if (IndicatorShape.TRIANGLE == mIndicatorShape){
            initTriangleLocation();
            canvas.drawPath(mTrianglePath,mTrianglePaint);
            return;
        }

        // 獲得控件的高度
        final int height = getHeight();
        float nextLeft;
        float nextRight;
        // 依據文字等長還是tab等長進行效果展示
        switch (mIndicatorType){
            case EQUAL_TAB:
                View currentTab = mLinearContainer.getChildAt(mCurrentIndex);
                mLineLeft = currentTab.getLeft();
                mLineRight = currentTab.getRight();
                if (mCurrentPositionOffset > 0f && mCurrentIndex < mTabCount - 1) {
                    View nextTab = mLinearContainer.getChildAt(mCurrentIndex + 1);
                    nextLeft = nextTab.getLeft();
                    nextRight = nextTab.getRight();
                    mLineLeft = mLineLeft + (nextLeft - mLineLeft) * mCurrentPositionOffset;
                    mLineRight = mLineRight + (nextRight - mLineRight) * mCurrentPositionOffset;
                }
                break;
            case EQUAL_TEXT:
                getTextLocation(mCurrentIndex);
                mLineLeft = textLocation.left;
                mLineRight = textLocation.right;
                if (mCurrentPositionOffset > 0f && mCurrentIndex < mTabCount - 1) {
                    getTextLocation(mCurrentIndex + 1);
                    nextLeft = textLocation.left;
                    nextRight = textLocation.right;
                    mLineLeft = mLineLeft + (nextLeft - mLineLeft) * mCurrentPositionOffset;
                    mLineRight = mLineRight + (nextRight - mLineRight) * mCurrentPositionOffset;
                }
                break;
            case ABSOLUTE_LENGTH:
                getLinearLayoutWithLength(mCurrentIndex);
                mLineLeft = textLocation.left;
                mLineRight = textLocation.right;
                if (mCurrentPositionOffset > 0f && mCurrentIndex < mTabCount-1){
                    getLinearLayoutWithLength(mCurrentIndex + 1);
                    nextLeft = textLocation.left;
                    nextRight = textLocation.right;
                    mLineLeft = mLineLeft + (nextLeft - mLineLeft) * mCurrentPositionOffset;
                    mLineRight = mLineRight + (nextRight - mLineRight) * mCurrentPositionOffset;
                }
                break;
                default:break;
        }
        canvas.drawRect(mLineLeft, height - mIndicatorPaint.getStrokeWidth()/2, mLineRight, height, mIndicatorPaint);
    }


    /*
     * ----------------- 準備繪製白色的三角形指示器 ---------
     */

    private void initTriangleLocation() {
        // 這裏有問題嘛
        float centerX = getTriangleCenterX(mCurrentIndex);
        if (mCurrentIndex < mTabCount-1){
            float nextCenterX = getTriangleCenterX(mCurrentIndex + 1);
            centerX = centerX + (nextCenterX - centerX) * mCurrentPositionOffset;
        }
        if (mTrianglePath == null){
            mTrianglePath = new Path();
        }
        mTrianglePath.reset();
        mTrianglePath.moveTo(centerX - mTriangleWidth/2,mIndicatorHeight);
        mTrianglePath.lineTo(centerX + mTriangleWidth/2, mIndicatorHeight);
        mTrianglePath.lineTo(centerX, mIndicatorHeight - mTriangleHeight);
        mTrianglePath.close();
    }

    /**
     * 獲得文本的中點
     * @param position
     */
    private float  getTriangleCenterX(int position){
        View child = mLinearContainer.getChildAt(position);
        int left = child.getLeft();
        int width = child.getWidth();
        return left + width/2f;
    }

    /*
     * ------------ 結束繪製白色的三角形指示器 ---------
     */



    /**
     * 獲得指定tab中,文字的left和right,線性指示器
     */
    @SuppressWarnings("ConstantConditions")
    private void getTextLocation(int position) {
        View tab = mLinearContainer.getChildAt(position);
        String tabText = mViewPager.getAdapter().getPageTitle(position).toString();
        float textWidth = mMeasureTextPaint.measureText(tabText);
        int tabWidth = tab.getWidth();
        textLocation.left = tab.getLeft() + (int) ((tabWidth - textWidth) / 2);
        textLocation.right = tab.getRight() - (int) ((tabWidth - textWidth) / 2);
    }


    /**
     * 根據定長繪製指示器
     * @param position
     */
    private void getLinearLayoutWithLength(int position){
        View child = mLinearContainer.getChildAt(position);
        int childLeft = child.getLeft();
        int childWidth = child.getWidth();
        textLocation.left = (int)(childLeft + childWidth / 2f - mIndicatorLength/2f);
        textLocation.right = (int)(childLeft + childWidth / 2f + mIndicatorLength/2f);
    }

    private LeftRight textLocation = new LeftRight();

    class LeftRight {
        int left, right;
    }


    /**
     * 滑動HorizontalScrollView 滾動方法
     * @param position          標示下標ID
     * @param currentTextLength 當前文本長度
     * 比較關鍵 ---- 執行scrollView的滑動效果
     *
     *  這是很清除的,當水平方向滑動,值爲正向左滑動
     */
    private void scrollToChild(int position, int currentTextLength) {
        // 如果文本長度爲空,或者平分模式則返回
        if (mTabCount == 0 || isExpand){
            return;
        }
        //getLeft():tab相對於父控件,即tabsContainer的left
        View child = mLinearContainer.getChildAt(position);
        int newScrollX = child.getLeft() + currentTextLength ;

        //附加一個偏移量,防止當前選中的tab太偏左
        //可以去掉看看是什麼效果

        // 相當於在原來基礎上向右滑 mScrollOffset的距離,就達到了太左或者太有可以往中間靠的效果
        if (position > 0 || currentTextLength > 0) {
            newScrollX -= mScrollOffset;
        }

        if (newScrollX != lastScrollX) {
            lastScrollX = newScrollX;
            scrollTo(newScrollX, 0);
        }
    }


    /**
     * 重寫onClickListener,爲了能夠安全的將參數傳遞進來
     */
    private class TextClickSelectListener implements View.OnClickListener{
        private  int selectedIndex;
        TextClickSelectListener(int selectedIndex) {
            this.selectedIndex = selectedIndex;
        }
        @Override
        public void onClick(View v) {
            // 設置文本的選中----
            if (mViewPager != null){
                mViewPager.setCurrentItem(selectedIndex);
            }
        }
    }


    /**
     * 重寫ViewPager的onPageChangeListener 目的在於監聽滑動狀態,進行綁定
     */
    private class NicePageChangeListener implements ViewPager.OnPageChangeListener{

        @Override
        public void onPageScrolled(int i, float v, int i1) {
            mCurrentIndex = i;
            mCurrentPositionOffset = v;
            //HorizontalScrollView滾動
            scrollToChild(i, (int) (v * mLinearContainer.getChildAt(i).getWidth()));
            //invalidate後onDraw會被調用,繪製指示器
            invalidate();
        }

        @Override
        public void onPageSelected(int i) {
            mSelectedIndex = i;
            updateTextTabStyle();
        }

        @Override
        public void onPageScrollStateChanged(int i) {

        }
    }

}

使用方法:

   indicator = (NiceViewPagerIndicator) findViewById(R.id.niceIndicator2);
        indicator.setIndicatorLengthType(NiceViewPagerIndicator.IndicatorType.EQUAL_TEXT)
                .setIndicatorShapeType(NiceViewPagerIndicator.IndicatorShape.LINEAR)
                .setIndicatorColor(Color.BLUE);
        indicator.setUpViewPager(mUsbViewPager);

        mUsbViewPager.addOnPageChangeListener(this);
        ```
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章