模仿微博ViewPage指示器滑動(dòng)效果的自定義View

github地址:https://github.com/shallcheek/CaterpillarIndicator

效果圖

偶然看到在微博的最新版本看到發(fā)現(xiàn)界面和個(gè)人界面的幾個(gè)Tab滑動(dòng)的指示器添加了動(dòng)畫,看起來比以前的線條滑動(dòng)看起來生動(dòng)些。

實(shí)現(xiàn)

實(shí)現(xiàn)滑動(dòng)是我們Android開發(fā)經(jīng)常運(yùn)用到的ViewPage 線條的滑動(dòng)則是根據(jù)ViewPage 的Select Item 標(biāo)識(shí)決定的。而相應(yīng)的滑動(dòng)動(dòng)畫效果則是根據(jù)ViewPage的監(jiān)聽事件OnPageChangeListener 中對(duì)應(yīng)的回調(diào)方法

public void onPageScrolled(int position,float positionOffset,int positionOffsetPixels)

positionOffsetPixels是滑動(dòng)距離,和屏幕有關(guān),*手指往左滑動(dòng),該值遞增,反之遞減 對(duì)應(yīng)的可以判斷左右滑動(dòng)然后根據(jù)值對(duì)線條做相應(yīng)的處理

滑動(dòng)距離計(jì)算公式

mCurrentScroll=控件的寬度*當(dāng)前的position+positionOffsetPixels*(控件的寬度/ViewPage寬度)

scroll_x = (mCurrentScroll- ((選擇的ITEM) * (Indicator的寬度))) /總的ITEM數(shù)量

實(shí)現(xiàn)代碼

@Override
public voidonPageScrolled(intposition, floatpositionOffset, intpositionOffsetPixels) {
floata = (float) getWidth() / (float)mViewPager.getWidth();
onScrolled((int) ((getWidth() +mViewPager.getPageMargin()) * position + positionOffsetPixels * a));
}


計(jì)算出的的scroll_x則是線條的左右滑動(dòng)距離 線條往左邊移動(dòng)是負(fù)數(shù)往右邊是正數(shù)
所以線條長短變化實(shí)現(xiàn)代碼如下:


 //獲取單個(gè)item線條的寬度 不能超過View的寬度平分的寬度
  this.mItemLineWidth = mItemLineWidth > cursorWidth ? cursorWidth : mItemLineWidth;
  int mItemLeft;
  int mItemRight;
     if (mItemLineWidth < cursorWidth) {
            mItemLeft = (cursorWidth - mItemLineWidth) / 2;
            mItemRight = cursorWidth - mItemLeft;
        } else {
            mItemLeft = 0;
            mItemRight = cursorWidth;
        }

            boolean isHalf = Math.abs(scroll_x) < (cursorWidth / 2);
            if (scroll_x < 0) {
                if (isHalf) {
                    leftX = (mSelectedTab) * cursorWidth + scroll_x * 2 + mItemLeft;
                    rightX = (mSelectedTab) * cursorWidth + mItemRight;
                } else {
                    leftX = (mSelectedTab - 1) * cursorWidth + mItemLeft;
                    rightX = (mSelectedTab) * cursorWidth + mItemRight + (scroll_x + (cursorWidth / 2)) * 2;
                }
            } else if (scroll_x > 0) {
                if (isHalf) {
                    leftX = mSelectedTab * cursorWidth + mItemLeft;
                    rightX = (mSelectedTab) * cursorWidth + mItemRight + scroll_x * 2;
                } else {
                    leftX = mSelectedTab * cursorWidth + mItemLeft + (scroll_x - (cursorWidth / 2)) * 2;
                    rightX = (mSelectedTab + 1) * cursorWidth + mItemRight;
                }

            } else {
                leftX = mSelectedTab * cursorWidth + mItemLeft;
                rightX = mSelectedTab * cursorWidth + mItemRight;
            }
        }

接下來便就是根據(jù)線條的最左邊和最右邊距離的點(diǎn)便可以在View上面繪制出線條

        float bottomY = getHeight();
        float topY = bottomY - mFooterLineHeight;

        drawLineRect.left = leftX;
        drawLineRect.right = rightX;
        drawLineRect.bottom = bottomY;
        drawLineRect.top = topY;

        int roundXY = isRoundRectangleLine ? (mFooterLineHeight / 2) : 0;
        canvas.drawRoundRect(drawLineRect, roundXY, roundXY, mPaintFooterLine);

完整代碼

public class CaterpillarIndicator extends LinearLayout implements View.OnClickListener, ViewPager.OnPageChangeListener {
    private static final String TAG = "CaterpillarIndicator";

    private static final int BASE_ID = 0xffff00;
    private static final int FOOTER_COLOR = 0xFFFFC445;
    private static final int ITEM_TEXT_COLOR_NORMAL = 0xFF999999;
    private static final int ITEM_TEXT_COLOR_SELECT = 0xFFFFC445;

    private boolean isRoundRectangleLine = true;
    private boolean isCaterpillar = true;
    private int mFootLineColor;
    private int mTextSizeNormal;
    private int mTextSizeSelected;
    private int mTextColorNormal;
    private int mTextColorSelected;
    private int mFooterLineHeight;
    private int mItemLineWidth;
    /**
     * item count
     */
    private int mItemCount = 0;
    private int mCurrentScroll = 0;
    private int mSelectedTab = 0;
    private boolean isNewClick;
    /**
     * item view id index
     */
    private int mCurrID = 0;
    private List<TitleInfo> mTitles;
    private ViewPager mViewPager;
    /**
     * indicator line is Rounded Rectangle
     */

    /**
     * line paint
     */
    private Paint mPaintFooterLine;
    /**
     * line RectF
     */
    private RectF drawLineRect;


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

    public CaterpillarIndicator(Context context, AttributeSet attrs) {
        super(context, attrs);
        setFocusable(true);
        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CaterpillarIndicator);
        mFootLineColor = a.getColor(R.styleable.CaterpillarIndicator_slide_footer_color, FOOTER_COLOR);
        mTextSizeNormal = a.getDimensionPixelSize(R.styleable.CaterpillarIndicator_slide_text_size_normal, dip2px(mTextSizeNormal));
        mTextSizeSelected = a.getDimensionPixelSize(R.styleable.CaterpillarIndicator_slide_text_size_selected, dip2px(mTextSizeNormal));
        mFooterLineHeight = a.getDimensionPixelOffset(R.styleable.CaterpillarIndicator_slide_footer_line_height, dip2px(3));
        mTextColorSelected = a.getColor(R.styleable.CaterpillarIndicator_slide_text_color_selected, ITEM_TEXT_COLOR_SELECT);
        mTextColorNormal = a.getColor(R.styleable.CaterpillarIndicator_slide_text_color_normal, ITEM_TEXT_COLOR_NORMAL);
        isCaterpillar = a.getBoolean(R.styleable.CaterpillarIndicator_slide_caterpillar, true);
        isRoundRectangleLine = a.getBoolean(R.styleable.CaterpillarIndicator_slide_round, true);
        mItemLineWidth = (int) a.getDimension(R.styleable.CaterpillarIndicator_slide_item_width, dip2px(24));

        setWillNotDraw(false);
        initDraw();
        a.recycle();
    }


    /**
     * set foot line height
     *
     * @param mFooterLineHeight foot line height (int)
     */
    public void setFooterLineHeight(int mFooterLineHeight) {
        this.mFooterLineHeight = dip2px(mFooterLineHeight);
        invalidate();
    }


    /**
     * item width
     *
     * @param mItemLineWidth item width(dp)
     */
    public void setItemLineWidth(int mItemLineWidth) {
        this.mItemLineWidth = dip2px(mItemLineWidth);
        invalidate();
    }


    public void setCaterpillar(boolean caterpillar) {
        isCaterpillar = caterpillar;
        invalidate();
    }

    /**
     * is round line
     *
     * @param roundRectangleLine true (yes) false (no )
     */
    public void setRoundRectangleLine(boolean roundRectangleLine) {
        isRoundRectangleLine = roundRectangleLine;
    }

    private void initDraw() {
        mPaintFooterLine = new Paint();
        mPaintFooterLine.setAntiAlias(true);
        mPaintFooterLine.setStyle(Paint.Style.FILL);
        drawLineRect = new RectF(0, 0, 0, 0);
    }


    @Override
    public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
        float a = (float) getWidth() / (float) mViewPager.getWidth();

        onScrolled((int) ((getWidth() + mViewPager.getPageMargin()) * position + positionOffsetPixels * a));
    }

    @Override
    public void onPageSelected(int position) {
        onSwitched(position);
    }

    @Override
    public void onPageScrollStateChanged(int state) {

    }

    public void setFootLineColor(int mFootLineColor) {
        this.mFootLineColor = mFootLineColor;
        invalidate();
    }


    /**
     * set text normal size(dp)
     *
     * @param mTextSizeNormal normal text size
     */
    public void setTextSizeNormal(int mTextSizeNormal) {
        this.mTextSizeNormal = dip2px(mTextSizeNormal);
        updateItemText();
    }

    public void setTextSizeSelected(int mTextSizeSelected) {
        this.mTextSizeSelected = dip2px(mTextSizeSelected);
        updateItemText();
    }

    public void setTextColorNormal(int mTextColorNormal) {
        this.mTextColorNormal = mTextColorNormal;
        updateItemText();
    }

    public void setTextColorSelected(int mTextColorSelected) {
        this.mTextColorSelected = mTextColorSelected;
        updateItemText();
    }

    private void updateItemText() {
        for (int i = 0; i < getChildCount(); i++) {
            View v = getChildAt(i);
            if (v instanceof TextView) {
                TextView tv = (TextView) v;
                if (tv.isSelected()) {
                    tv.setTextColor(mTextColorSelected);
                    tv.setTextSize(TypedValue.COMPLEX_UNIT_PX, mTextSizeSelected);
                } else {
                    tv.setTextColor(mTextColorNormal);
                    tv.setTextSize(TypedValue.COMPLEX_UNIT_PX, mTextSizeNormal);
                }
            }
        }

    }


    private int dip2px(float dpValue) {
        final float scale = Resources.getSystem().getDisplayMetrics().density;
        return (int) (dpValue * scale + 0.5f);
    }


    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        if (isNewClick) {
            isNewClick = false;
            return;
        }

        float scroll_x;
        int cursorWidth;
        if (mItemCount != 0) {
            cursorWidth = getWidth() / mItemCount;
            scroll_x = (mCurrentScroll - ((mSelectedTab) * (getWidth()))) / mItemCount;
        } else {
            cursorWidth = getWidth();
            scroll_x = mCurrentScroll;
        }

        this.mItemLineWidth = mItemLineWidth > cursorWidth ? cursorWidth : mItemLineWidth;
        int mItemLeft;
        int mItemRight;
        if (mItemLineWidth < cursorWidth) {
            mItemLeft = (cursorWidth - mItemLineWidth) / 2;
            mItemRight = cursorWidth - mItemLeft;
        } else {
            mItemLeft = 0;
            mItemRight = cursorWidth;
        }

        float leftX;
        float rightX;

        boolean isHalf = Math.abs(scroll_x) < (cursorWidth / 2);
        mPaintFooterLine.setColor(mFootLineColor);
        if (isCaterpillar) {
            if (scroll_x < 0) {
                if (isHalf) {
                    leftX = (mSelectedTab) * cursorWidth + scroll_x * 2 + mItemLeft;
                    rightX = (mSelectedTab) * cursorWidth + mItemRight;
                } else {
                    leftX = (mSelectedTab - 1) * cursorWidth + mItemLeft;
                    rightX = (mSelectedTab) * cursorWidth + mItemRight + (scroll_x + (cursorWidth / 2)) * 2;
                }
            } else if (scroll_x > 0) {
                if (isHalf) {
                    leftX = mSelectedTab * cursorWidth + mItemLeft;
                    rightX = (mSelectedTab) * cursorWidth + mItemRight + scroll_x * 2;
                } else {
                    leftX = mSelectedTab * cursorWidth + mItemLeft + (scroll_x - (cursorWidth / 2)) * 2;
                    rightX = (mSelectedTab + 1) * cursorWidth + mItemRight;
                }

            } else {
                leftX = mSelectedTab * cursorWidth + mItemLeft;
                rightX = mSelectedTab * cursorWidth + mItemRight;
            }
        } else {
            leftX = mSelectedTab * cursorWidth + scroll_x + mItemLeft;
            rightX = (mSelectedTab) * cursorWidth + scroll_x + mItemRight;

        }
        float bottomY = getHeight();
        //set foot line height
        float topY = bottomY - mFooterLineHeight;

        drawLineRect.left = leftX;
        drawLineRect.right = rightX;
        drawLineRect.bottom = bottomY;
        drawLineRect.top = topY;

        int roundXY = isRoundRectangleLine ? (mFooterLineHeight / 2) : 0;
        canvas.drawRoundRect(drawLineRect, roundXY, roundXY, mPaintFooterLine);
    }

    private void onScrolled(int h) {
        mCurrentScroll = h;
        Log.e(TAG, "onScrolled: onScrolled"+mCurrentScroll);
        invalidate();
    }

    public synchronized void onSwitched(int position) {
        if (mSelectedTab == position) {
            return;
        }
        setCurrentTab(position);

    }

    /**
     * init indication
     *
     * @param startPos   init select pos
     * @param tabs       title list
     * @param mViewPager ViewPage
     */
    public void init(int startPos, List<TitleInfo> tabs, ViewPager mViewPager) {
        removeAllViews();
        this.mViewPager = mViewPager;
        this.mViewPager.addOnPageChangeListener(this);
        this.mTitles = tabs;
        this.mItemCount = tabs.size();
        setWeightSum(mItemCount);
        for (int i = 0; i < mItemCount; i++) {
            add(tabs.get(i).getName());
        }
        mViewPager.setCurrentItem(startPos);
        invalidate();
        requestLayout();
    }


    protected void add(String label) {
        TextView view = new TextView(getContext());
        LinearLayout.LayoutParams params = new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT, 1);
        view.setGravity(Gravity.CENTER);
        view.setLayoutParams(params);
        view.setText(label);
        view.setTextSize(TypedValue.COMPLEX_UNIT_PX, mTextSizeNormal);
        view.setTextColor(mTextColorNormal);
        view.setId(BASE_ID + (mCurrID++));
        view.setOnClickListener(this);
        addView(view);
    }

    @Override
    public void onClick(View v) {
        int position = v.getId() - BASE_ID;
        isNewClick = true;
        mViewPager.setCurrentItem(position, true);
    }

    /**
     * get title list size
     *
     * @return list size
     */
    public int getTitleCount() {
        return mTitles != null ? mTitles.size() : 0;
    }

    public synchronized void setCurrentTab(int index) {
        if (index < 0 || index >= getTitleCount()) {
            return;
        }
        View oldTab = getChildAt(mSelectedTab);
        if (oldTab != null) {
            oldTab.setSelected(false);
            setTabTextSize(oldTab, false);
        }

        mSelectedTab = index;
        View newTab = getChildAt(mSelectedTab);
        if (newTab != null) {
            setTabTextSize(newTab, true);
            newTab.setSelected(true);
        }


    }

    /**
     * set select textView textSize&textColor state
     *
     * @param tab      TextView
     * @param selected is Select
     */
    private void setTabTextSize(View tab, boolean selected) {
        if (tab instanceof TextView) {
            TextView tv = (TextView) tab;
            tv.setTextSize(TypedValue.COMPLEX_UNIT_PX, selected ? mTextSizeSelected : mTextSizeNormal);
            tv.setTextColor(selected ? mTextColorSelected : mTextColorNormal);
        }
    }


    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        super.onLayout(changed, l, t, r, b);
        if (mCurrentScroll == 0 && mSelectedTab != 0) {
            mCurrentScroll = (getWidth() + mViewPager.getPageMargin()) * mSelectedTab;
        }
    }


    /**
     * title
     */
    public static class TitleInfo {
        String name;

        public TitleInfo(String name) {
            this.name = name;
        }

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }
    }
}

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容