成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專(zhuān)欄INFORMATION COLUMN

反射改變TabLayout屬性

GeekGhc / 2049人閱讀

摘要:第二種在原有基礎(chǔ)上通過(guò)繼承控件,重寫(xiě)其中幾個(gè)方法,并且通過(guò)反射來(lái)修改部分屬性,也能達(dá)到第一種方案效果。因此這里需要用反射替換成自己的滑動(dòng)監(jiān)聽(tīng),然后在的監(jiān)聽(tīng)類(lèi)中的方法,改變的顏色。通過(guò)反射找到源碼中成員變量,然后設(shè)置暴力訪問(wèn)權(quán)限。

目錄介紹

01.遇到的實(shí)際需求分析

02.原生TabLayout局限

03.TabLayout源碼解析

3.1 Tab選項(xiàng)卡如何實(shí)現(xiàn)

3.2 滑動(dòng)切換Tab選項(xiàng)卡

3.3 Tab選項(xiàng)卡指示線(xiàn)寬度

04.設(shè)置自定義tabView選項(xiàng)卡

05.自定義指示器的長(zhǎng)度

06.設(shè)置滑動(dòng)改變選項(xiàng)卡顏色

07.使用反射的注意要點(diǎn)

08.混淆時(shí)用到反射注意項(xiàng)

好消息

博客筆記大匯總【16年3月到至今】,包括Java基礎(chǔ)及深入知識(shí)點(diǎn),Android技術(shù)博客,Python學(xué)習(xí)筆記等等,還包括平時(shí)開(kāi)發(fā)中遇到的bug匯總,當(dāng)然也在工作之余收集了大量的面試題,長(zhǎng)期更新維護(hù)并且修正,持續(xù)完善……開(kāi)源的文件是markdown格式的!同時(shí)也開(kāi)源了生活博客,從12年起,積累共計(jì)N篇[近100萬(wàn)字,陸續(xù)搬到網(wǎng)上],轉(zhuǎn)載請(qǐng)注明出處,謝謝!

鏈接地址:https://github.com/yangchong2...

如果覺(jué)得好,可以star一下,謝謝!當(dāng)然也歡迎提出建議,萬(wàn)事起于忽微,量變引起質(zhì)變!

01.遇到的實(shí)際需求分析

實(shí)際開(kāi)發(fā)中UI的效果圖

一般要求文字內(nèi)容和指示線(xiàn)的寬度要一樣

使用TabLayout的效果圖

一般指示線(xiàn)的寬度要大于文字內(nèi)容

遇到問(wèn)題分析

設(shè)置tabPaddingStart和tabPaddingEnd,但是布局填上去后發(fā)現(xiàn)并沒(méi)有用。

實(shí)現(xiàn)方案

第一種:自定義類(lèi)似TabLayout的控件,代碼量巨大,且GitHub上有許多已經(jīng)比較成熟的庫(kù),代碼質(zhì)量是層次不齊。

第二種:在原有基礎(chǔ)上通過(guò)繼承TabLayout控件,重寫(xiě)其中幾個(gè)方法,并且通過(guò)反射來(lái)修改部分屬性,也能達(dá)到第一種方案效果。

下面就來(lái)講一下我自己通過(guò)第二種方案實(shí)現(xiàn)步驟和原理!

最終UI效果圖展示

02.原生TabLayout局限

一張圖看懂TabLayout的結(jié)構(gòu)

如果要用代碼進(jìn)行表示的話(huà),大概是這樣的。TabLayout繼承自HorizontalScrollView,而都知道ScrollView只能添加一個(gè)子 View,所以SlidingTabIndicator就是那個(gè)用來(lái)添加子View 的橫向LinearLayout。

//28版本代碼
public class TabLayout extends HorizontalScrollView {

    private class SlidingTabIndicator extends LinearLayout {

    }
}

存在的局限性

第一個(gè)無(wú)法改變指示線(xiàn)的寬度

第二個(gè)無(wú)法做到滑動(dòng)改變tab選項(xiàng)卡顏色漸變的效果【有的還需要放大效果】

03.TabLayout源碼解析 3.1 Tab選項(xiàng)卡如何實(shí)現(xiàn)

第一種方式,直接通過(guò)addTab方法添加tab選項(xiàng)卡,代碼如下所示

TabLayout.Tab tab = tabLayout.newTab();
View tabView = new TextView(this);
tabLayout.setCustomView(tabView);
tabLayout.addTab(tab);

第二種方式,通過(guò)設(shè)置FragmentPagerAdapter中的getPageTitle也可以添加tab選項(xiàng)卡,代碼如下所示

mTitleList.add("瀟湘劍雨");
FragmentManager supportFragmentManager = getSupportFragmentManager();
PagerAdapter myAdapter = new PagerAdapter(supportFragmentManager, mFragments, mTitleList);
tabLayout.setAdapter(myAdapter);


public class PagerAdapter extends FragmentPagerAdapter {

    private List mFragment;
    private List mTitleList;

    public PagerAdapter(FragmentManager fm, List mFragment, List mTitleList) {
        super(fm);
        this.mFragment = mFragment;
        this.mTitleList = mTitleList;
    }

    @Override
    public CharSequence getPageTitle(int position) {
        if (mTitleList != null) {
            return mTitleList.get(position);
        } else {
            return "";
        }
    }
}

接下來(lái)看一下tabLayout源碼是如何拿到getPageTitle方法的內(nèi)容而達(dá)到設(shè)置addTab的目的。主要看源碼中的populateFromPagerAdapter方法??吹较旅娲a是不是豁然開(kāi)朗了……

void populateFromPagerAdapter() {
    this.removeAllTabs();
    if (this.pagerAdapter != null) {
        int adapterCount = this.pagerAdapter.getCount();

        int curItem;
        for(curItem = 0; curItem < adapterCount; ++curItem) {
            this.addTab(this.newTab().setText(this.pagerAdapter.getPageTitle(curItem)), false);
        }

        if (this.viewPager != null && adapterCount > 0) {
            curItem = this.viewPager.getCurrentItem();
            if (curItem != this.getSelectedTabPosition() && curItem < this.getTabCount()) {
                this.selectTab(this.getTabAt(curItem));
            }
        }
    }
}

不管是上面那種方式,那么如何將tab添加到SlidingTabIndicator布局中呢?

通過(guò)下面代碼可以看到,最終是通過(guò)slidingTabIndicator對(duì)象調(diào)用addView將tabView添加到SlidingTabIndicator布局之中的。

public void addTab(@NonNull TabLayout.Tab tab, int position, boolean setSelected) {
    if (tab.parent != this) {
        throw new IllegalArgumentException("Tab belongs to a different TabLayout.");
    } else {
        this.configureTab(tab, position);
        this.addTabView(tab);
        if (setSelected) {
            tab.select();
        }
    }
}

private void addTabView(TabLayout.Tab tab) {
    TabLayout.TabView tabView = tab.view;
    this.slidingTabIndicator.addView(tabView, tab.getPosition(), this.createLayoutParamsForTabs());
}

為什么要分析這個(gè)addTab?

因?yàn)樾枨笳f(shuō)了,需要在滑動(dòng)的時(shí)候,隨著滑動(dòng)而改變tabView的文字顏色,這一點(diǎn)原生TabLayout并沒(méi)有實(shí)現(xiàn)。所以要實(shí)現(xiàn)這個(gè)邏輯,就必須重寫(xiě)TabLayout的addTab方法,然后將自己自定義的tabView添加到tab中,這個(gè)下面會(huì)講如何實(shí)現(xiàn)……

3.2 滑動(dòng)切換Tab選項(xiàng)卡

第一步:隨著頁(yè)面的滑動(dòng)文字顏色漸變那么肯定少不了ViewPager的頁(yè)面監(jiān)聽(tīng),這個(gè)在我們調(diào)用setupWithViewPager的時(shí)候TabLayout就已經(jīng)添加監(jiān)聽(tīng)。那么先來(lái)看下源碼監(jiān)聽(tīng)滑動(dòng)是如何實(shí)現(xiàn)的?

綁定 ViewPager 只需要一行代碼mTabLayout.setupWithViewPager(mViewPager)即可。

可以看到當(dāng)viewPager不為null的時(shí)候,先移除listener監(jiān)聽(tīng)事件。然后在創(chuàng)建listener監(jiān)聽(tīng),并且重置狀態(tài)。

private void setupWithViewPager(@Nullable ViewPager viewPager, boolean autoRefresh, boolean implicitSetup) {
    if (this.viewPager != null) {
        if (this.pageChangeListener != null) {
            this.viewPager.removeOnPageChangeListener(this.pageChangeListener);
        }

        if (this.adapterChangeListener != null) {
            this.viewPager.removeOnAdapterChangeListener(this.adapterChangeListener);
        }
    }

    if (this.currentVpSelectedListener != null) {
        this.removeOnTabSelectedListener(this.currentVpSelectedListener);
        this.currentVpSelectedListener = null;
    }

    if (viewPager != null) {
        this.viewPager = viewPager;
        if (this.pageChangeListener == null) {
            this.pageChangeListener = new TabLayout.TabLayoutOnPageChangeListener(this);
        }

        this.pageChangeListener.reset();
        viewPager.addOnPageChangeListener(this.pageChangeListener);
        this.currentVpSelectedListener = new TabLayout.ViewPagerOnTabSelectedListener(viewPager);
        this.addOnTabSelectedListener(this.currentVpSelectedListener);
        PagerAdapter adapter = viewPager.getAdapter();
        if (adapter != null) {
            this.setPagerAdapter(adapter, autoRefresh);
        }

        if (this.adapterChangeListener == null) {
            this.adapterChangeListener = new TabLayout.AdapterChangeListener();
        }

        this.adapterChangeListener.setAutoRefresh(autoRefresh);
        viewPager.addOnAdapterChangeListener(this.adapterChangeListener);
        this.setScrollPosition(viewPager.getCurrentItem(), 0.0F, true);
    } else {
        this.viewPager = null;
        this.setPagerAdapter((PagerAdapter)null, false);
    }

    this.setupViewPagerImplicitly = implicitSetup;
}

那么滑動(dòng)是如何切換選項(xiàng)卡和指示線(xiàn)呢,具體看一下TabLayoutOnPageChangeListener滑動(dòng)監(jiān)聽(tīng)源碼。

主要是看onPageSelected方法,該方法是通過(guò)tabLayout.selectTab來(lái)切換選項(xiàng)卡的。

public static class TabLayoutOnPageChangeListener implements OnPageChangeListener {

    public TabLayoutOnPageChangeListener(TabLayout tabLayout) {
        this.tabLayoutRef = new WeakReference(tabLayout);
    }

    public void onPageScrollStateChanged(int state) {
        this.previousScrollState = this.scrollState;
        this.scrollState = state;
    }

    public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
        TabLayout tabLayout = (TabLayout)this.tabLayoutRef.get();
        if (tabLayout != null) {
            boolean updateText = this.scrollState != 2 || this.previousScrollState == 1;
            boolean updateIndicator = this.scrollState != 2 || this.previousScrollState != 0;
            tabLayout.setScrollPosition(position, positionOffset, updateText, updateIndicator);
        }

    }

    public void onPageSelected(int position) {
        TabLayout tabLayout = (TabLayout)this.tabLayoutRef.get();
        if (tabLayout != null && tabLayout.getSelectedTabPosition() != position && position < tabLayout.getTabCount()) {
            boolean updateIndicator = this.scrollState == 0 || this.scrollState == 2 && this.previousScrollState == 0;
            tabLayout.selectTab(tabLayout.getTabAt(position), updateIndicator);
        }
    }
}

知道了滑動(dòng)切換選項(xiàng)卡后,就思考一下,能否通過(guò)反射來(lái)使用自己的滑動(dòng)監(jiān)聽(tīng)事件,然后在onPageSelected方法中,滑動(dòng)改變選項(xiàng)卡中文字的顏色,或者縮放的功能呢。答案是可以的。

3.3 Tab選項(xiàng)卡指示線(xiàn)寬度

具體可以看updateIndicatorPosition源碼

可以看到先獲取當(dāng)前滑動(dòng)位置的tabView,如果內(nèi)容不為空,則獲取左右的位置。

在滑塊滑動(dòng)的時(shí)候,如果滑動(dòng)超過(guò)了上一個(gè)或是下一個(gè)滑塊一半的話(huà)。那就說(shuō)明移動(dòng)到了上一個(gè)或是下一個(gè)滑塊,然后取出left和right

最后設(shè)置滑塊的位置

private void updateIndicatorPosition() {
    //根據(jù)當(dāng)前滑塊的位置拿到當(dāng)前TabView
    View selectedTitle = this.getChildAt(this.selectedPosition);
    int left;
    int right;
    if (selectedTitle != null && selectedTitle.getWidth() > 0) {
        //拿到TabView的左、右位置
        left = selectedTitle.getLeft();
        right = selectedTitle.getRight();
        if (!TabLayout.this.tabIndicatorFullWidth && selectedTitle instanceof TabLayout.TabView) {
            this.calculateTabViewContentBounds((TabLayout.TabView)selectedTitle, TabLayout.this.tabViewContentBounds);
            left = (int)TabLayout.this.tabViewContentBounds.left;
            right = (int)TabLayout.this.tabViewContentBounds.right;
        }

        //在滑塊滑動(dòng)的時(shí)候,如果滑動(dòng)超過(guò)了上一個(gè)或是下一個(gè)滑塊一半的話(huà)
        //那就說(shuō)明移動(dòng)到了上一個(gè)或是下一個(gè)滑塊,然后取出left和right
        if (this.selectionOffset > 0.0F && this.selectedPosition < this.getChildCount() - 1) {
            View nextTitle = this.getChildAt(this.selectedPosition + 1);
            int nextTitleLeft = nextTitle.getLeft();
            int nextTitleRight = nextTitle.getRight();
            if (!TabLayout.this.tabIndicatorFullWidth && nextTitle instanceof TabLayout.TabView) {
                this.calculateTabViewContentBounds((TabLayout.TabView)nextTitle, TabLayout.this.tabViewContentBounds);
                nextTitleLeft = (int)TabLayout.this.tabViewContentBounds.left;
                nextTitleRight = (int)TabLayout.this.tabViewContentBounds.right;
            }

            left = (int)(this.selectionOffset * (float)nextTitleLeft + (1.0F - this.selectionOffset) * (float)left);
            right = (int)(this.selectionOffset * (float)nextTitleRight + (1.0F - this.selectionOffset) * (float)right);
        }
    } else {
        right = -1;
        left = -1;
    }
    //設(shè)置滑塊的位置
    this.setIndicatorPosition(left, right);
}

然后看一下setIndicatorPosition的代碼

設(shè)置滑塊的寬度是根據(jù)子TabView的寬度來(lái)設(shè)置的,也就是說(shuō),TabView的寬度是多少,那么滑塊的寬度就是多少。

void setIndicatorPosition(int left, int right) {
    if (left != this.indicatorLeft || right != this.indicatorRight) {
        this.indicatorLeft = left;
        this.indicatorRight = right;
        ViewCompat.postInvalidateOnAnimation(this);
    }
}

為何要分析這個(gè)?

因?yàn)槿绻阋淖冎甘酒鞯膶挾?,那么必須要能夠?dòng)態(tài)改變左右的位置。知道了這個(gè)大概的原理,那么下面利用反射設(shè)置選項(xiàng)卡左右的間距來(lái)改變指示器的長(zhǎng)度就知道怎么實(shí)現(xiàn)呢。

04.實(shí)現(xiàn)滑動(dòng)改變顏色

滑動(dòng)改變指示器文字變色

TabLayout中可以設(shè)置文字內(nèi)容,通過(guò)上面3.2源碼分析,可以知道通過(guò)addTab添加自定義選項(xiàng)卡,那么滑動(dòng)改變選項(xiàng)卡tabView的顏色,可以會(huì)涉及到監(jiān)聽(tīng)滑動(dòng)。因此這里需要用反射替換成自己的滑動(dòng)監(jiān)聽(tīng),然后在TabLayoutOnPageChangeListener的監(jiān)聽(tīng)類(lèi)中的onPageScrolled方法,改變tabView的顏色。

通過(guò)反射找到源碼中pageChangeListener成員變量,然后設(shè)置暴力訪問(wèn)權(quán)限。

然后獲取TabLayoutOnPageChangeListener的對(duì)象,刪除自帶的監(jiān)聽(tīng),同時(shí)將自己自定義的滑動(dòng)監(jiān)聽(tīng)listener添加上。

@Override
public void setupWithViewPager(@Nullable ViewPager viewPager, boolean autoRefresh) {
    super.setupWithViewPager(viewPager, autoRefresh);
    try {
        //通過(guò)反射找到mPageChangeListener
        Field field = getPageChangeListener();
        field.setAccessible(true);
        TabLayoutOnPageChangeListener listener = (TabLayoutOnPageChangeListener) field.get(this);
        if (listener!=null && viewPager!=null) {
            //刪除自帶監(jiān)聽(tīng)
            viewPager.removeOnPageChangeListener(listener);
            OnPageChangeListener mPageChangeListener = new OnPageChangeListener(this);
            mPageChangeListener.reset();
            viewPager.addOnPageChangeListener(mPageChangeListener);
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
}

然后看一下反射的代碼,我在網(wǎng)上看到好多博客,沒(méi)有區(qū)分27前和28后的問(wèn)題。這個(gè)地方一定要注意一下!

/**
 * 反射獲取私有的mPageChangeListener屬性,考慮support 28以后變量名修改的問(wèn)題
 * @return Field
 * @throws NoSuchFieldException
 */
private Field getPageChangeListener() throws NoSuchFieldException {
    Class clazz = TabLayout.class;
    try {
        // support design 27及一下版本
        return clazz.getDeclaredField("mPageChangeListener");
    } catch (NoSuchFieldException e) {
        e.printStackTrace();
        // 可能是28及以上版本
        return clazz.getDeclaredField("pageChangeListener");
    }
}

然后看一下自定義的OnPageChangeListener

采用弱引用方式防止監(jiān)聽(tīng)listener內(nèi)存泄漏,算是一個(gè)小的優(yōu)化

/**
 * 滑動(dòng)監(jiān)聽(tīng),核心邏輯
 * 建議如果是activity退到后臺(tái),或者關(guān)閉頁(yè)面,將listener給remove掉
 * 采用弱引用方式防止監(jiān)聽(tīng)listener內(nèi)存泄漏,算是一個(gè)小的優(yōu)化
 */
private static class OnPageChangeListener extends TabLayoutOnPageChangeListener {

    private final WeakReference mTabLayoutRef;
    private int mPreviousScrollState;
    private int mScrollState;

    OnPageChangeListener(TabLayout tabLayout) {
        super(tabLayout);
        mTabLayoutRef = new WeakReference<>((CustomTabLayout) tabLayout);
    }

    /**
     * 這個(gè)方法是滾動(dòng)狀態(tài)發(fā)生變化是調(diào)用
     * @param state                     樁體
     */
    @Override
    public void onPageScrollStateChanged(final int state) {
        mPreviousScrollState = mScrollState;
        mScrollState = state;
    }

    /**
     * 正在滾動(dòng)時(shí)調(diào)用
     * @param position                  索引
     * @param positionOffset            offset偏移
     * @param positionOffsetPixels      offsetPixels
     */
    @Override
    public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
        super.onPageScrolled(position, positionOffset, positionOffsetPixels);
        CustomTabLayout tabLayout = mTabLayoutRef.get();
        if (tabLayout == null) {
            return;
        }
        final boolean updateText = mScrollState != SCROLL_STATE_SETTLING ||
                mPreviousScrollState == SCROLL_STATE_DRAGGING;
        if (updateText) {
            tabLayout.tabScrolled(position, positionOffset);
        }
    }

    /**
     * 選中時(shí)調(diào)用
     * @param position                      索引
     */
    @Override
    public void onPageSelected(int position) {
        super.onPageSelected(position);
        CustomTabLayout tabLayout = mTabLayoutRef.get();
        mPreviousScrollState = SCROLL_STATE_SETTLING;
        tabLayout.setSelectedView(position);
    }

    /**
     * 重置狀態(tài)
     */
    void reset() {
        mPreviousScrollState = mScrollState = SCROLL_STATE_IDLE;
    }
}

05.自定義指示器的長(zhǎng)度

通過(guò)反射的方式修改指示器長(zhǎng)度,如果需要指示器寬度等于文字寬度需要自己微調(diào),或者28版本直接通過(guò)設(shè)置app:tabIndicatorFullWidth="false"屬性即可讓內(nèi)容和指示器寬度一樣。

原理就是通過(guò)反射的方式獲取TabLayout的字段mTabStrip(27之前)或者slidingTabIndicator(28之后),然后再去遍歷修改每一個(gè)子 View 的 Margin 值。代碼如下:

/**
 * 通過(guò)反射設(shè)置TabLayout每一個(gè)的長(zhǎng)度
 * @param left                      左邊 Margin 單位 dp
 * @param right                     右邊 Margin 單位 dp
 */
public void setIndicator(int left, int right) {
    Field tabStrip = null;
    try {
        tabStrip = getTabStrip();
        tabStrip.setAccessible(true);
    } catch (NoSuchFieldException e) {
        e.printStackTrace();
    }

    LinearLayout llTab = null;
    try {
        if (tabStrip != null) {
            llTab = (LinearLayout) tabStrip.get(this);
        }
    } catch (Exception e) {
        e.printStackTrace();
    }

    int l = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, left,
            Resources.getSystem().getDisplayMetrics());
    int r = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, right,
            Resources.getSystem().getDisplayMetrics());

    if (llTab != null) {
        for (int i = 0; i < llTab.getChildCount(); i++) {
            View child = llTab.getChildAt(i);
            child.setPadding(0, 0, 0, 0);
            LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
                    0, LinearLayout.LayoutParams.MATCH_PARENT, 1);
            params.leftMargin = l;
            params.rightMargin = r;
            child.setLayoutParams(params);
            child.invalidate();
        }
    }
}

然后看一下反射獲取tabStrip的代碼

/**
 * 反射獲取私有的mTabStrip屬性,考慮support 28以后變量名修改的問(wèn)題
 * @return Field

 */
private Field getTabStrip() throws NoSuchFieldException {
    Class clazz = TabLayout.class;
    try {
        // support design 27及一下版本
        return clazz.getDeclaredField("mTabStrip");
    } catch (NoSuchFieldException e) {
        e.printStackTrace();
        // 可能是28及以上版本
        return clazz.getDeclaredField("slidingTabIndicator");
    }
}
```

這里其實(shí)也可以不用反射,那么該怎么實(shí)現(xiàn)呢?

需要注意一點(diǎn),需要在Tablayout設(shè)置完成后操作,并且必須等所有繪制操作結(jié)束,使用tabLayout.post拿到屬性參數(shù),然后設(shè)置下margin。

public void setTabWidth(TabLayout tabLayout){
    //拿到slidingTabIndicator的布局
    LinearLayout mTabStrip = (LinearLayout) tabLayout.getChildAt(0);
    //遍歷SlidingTabStrip的所有TabView子view
    for (int i = 0; i < mTabStrip.getChildCount(); i++) {
        View tabView = mTabStrip.getChildAt(i);
        LinearLayout.LayoutParams params = (LinearLayout.LayoutParams)tabView.getLayoutParams();
        //給TabView設(shè)置leftMargin和rightMargin
        params.leftMargin = dp2px(10);
        params.rightMargin = dp2px(10);
        tabView.setLayoutParams(params);
        //觸發(fā)繪制
        tabView.invalidate();
    }
}

06.設(shè)置滑動(dòng)改變選項(xiàng)卡顏色

滑動(dòng)時(shí)如何改變選項(xiàng)卡的顏色呢?當(dāng)然在滾動(dòng)的時(shí)候去動(dòng)態(tài)改變屬性,具體的做法:

在TabLayoutOnPageChangeListener中監(jiān)聽(tīng),主要看onPageScrolled方法

@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
    super.onPageScrolled(position, positionOffset, positionOffsetPixels);
    CustomTabLayout tabLayout = mTabLayoutRef.get();
    if (tabLayout == null) {
        return;
    }
    final boolean updateText = mScrollState != SCROLL_STATE_SETTLING ||
            mPreviousScrollState == SCROLL_STATE_DRAGGING;
    if (updateText) {
        tabLayout.tabScrolled(position, positionOffset);
    }
}

然后看一下tabScrolled方法,代碼如下所示

這個(gè)方法里,主要是拿到當(dāng)前tabView和下一個(gè)tabView,然后依次改變Progress進(jìn)度,以此達(dá)到更改文字的顏色。

/**
 * 滑動(dòng)改變自定義tabView的顏色
 * @param position                      索引
 * @param positionOffset                偏移量
 */
private void tabScrolled(int position, float positionOffset) {
    if (positionOffset == 0.0F) {
        return;
    }
    //當(dāng)前tabView
    CustomTabView currentTrackView = getCustomTabView(position);
    //下一個(gè)tabView
    CustomTabView nextTrackView = getCustomTabView(position + 1);
    if (currentTrackView != null) {
        currentTrackView.setDirection(1);
        currentTrackView.setProgress(1.0F - positionOffset);
    }
    if (nextTrackView != null) {
        nextTrackView.setDirection(0);
        nextTrackView.setProgress(positionOffset);
    }
}

然后在CustomTabView中,看代碼如下所示

調(diào)用invalidate()方法會(huì)調(diào)用onDraw()方法,然后去達(dá)到重繪view的目的。

public void setProgress(float progress) {
    this.mProgress = progress;
    invalidate();
}

接著看看onDraw這個(gè)方法做了什么操作

@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    if (mDirection == DIRECTION_LEFT) {
        drawChangeLeft(canvas);
        drawOriginLeft(canvas);
    } else if (mDirection == DIRECTION_RIGHT) {
        drawOriginRight(canvas);
        drawChangeRight(canvas);
    } else if (mDirection == DIRECTION_TOP) {
        drawOriginTop(canvas);
        drawChangeTop(canvas);
    } else if (mDirection == DIRECTION_BOTTOM){
        drawOriginBottom(canvas);
        drawChangeBottom(canvas);
    }
}

然后看其中的一個(gè)drawChangeLeft方法

private void drawChangeLeft(Canvas canvas) {
    drawTextHor(canvas, mTextChangeColor, mTextStartX,  (int) (mTextStartX + mProgress * mTextWidth));
}

/**
 * 橫向
 * @param canvas                    畫(huà)板
 * @param color                     顏色
 * @param startX                    開(kāi)始x

 */
private void drawTextHor(Canvas canvas, int color, int startX, int endX) {
    mPaint.setColor(color);
    if (debug) {
        mPaint.setStyle(Style.STROKE);
        canvas.drawRect(startX, 0, endX, getMeasuredHeight(), mPaint);
    }
    canvas.save();
    canvas.clipRect(startX, 0, endX, getMeasuredHeight());
    // right, bottom
    canvas.drawText(mText, mTextStartX, getMeasuredHeight() / 2
                    - ((mPaint.descent() + mPaint.ascent()) / 2), mPaint);
    canvas.restore();
}
```


07.使用反射的注意要點(diǎn)

比如或者mTabStrip屬性,網(wǎng)上許多沒(méi)有區(qū)分27和28名稱(chēng)的變化。如果因?yàn)槊Q(chēng)的問(wèn)題,會(huì)導(dǎo)致反射獲取不到Field,那么所做的操作也就失效了,這是一個(gè)很大的風(fēng)險(xiǎn)。

/**
 * 反射獲取私有的mTabStrip屬性,考慮support 28以后變量名修改的問(wèn)題
 * @return Field

 */
private Field getTabStrip() throws NoSuchFieldException {
    Class clazz = TabLayout.class;
    try {
        // support design 27及一下版本
        return clazz.getDeclaredField("mTabStrip");
    } catch (NoSuchFieldException e) {
        e.printStackTrace();
        // 可能是28及以上版本
        return clazz.getDeclaredField("slidingTabIndicator");
    }
}
```


08.混淆時(shí)用到反射注意項(xiàng)

還有一點(diǎn)就是有的人這么使用會(huì)報(bào)錯(cuò),是因?yàn)榛煜a(chǎn)生的問(wèn)題,反射slidingTabIndicator或者pageChangeListener的時(shí)候可能會(huì)出問(wèn)題,可以在混淆配置里面設(shè)置下TabLayout不被混淆。

-keep class android.support.design.widget.TabLayout{*;}

其他介紹 01.關(guān)于博客匯總鏈接

1.技術(shù)博客匯總

2.開(kāi)源項(xiàng)目匯總

3.生活博客匯總

4.喜馬拉雅音頻匯總

5.其他匯總

02.關(guān)于我的博客

github:https://github.com/yangchong211

知乎:https://www.zhihu.com/people/...

簡(jiǎn)書(shū):http://www.jianshu.com/u/b7b2...

csdn:http://my.csdn.net/m0_37700275

喜馬拉雅聽(tīng)書(shū):http://www.ximalaya.com/zhubo...

開(kāi)源中國(guó):https://my.oschina.net/zbj161...

泡在網(wǎng)上的日子:http://www.jcodecraeer.com/me...

郵箱:[email protected]

阿里云博客:https://yq.aliyun.com/users/a... 239.headeruserinfo.3.dT4bcV

segmentfault頭條:https://segmentfault.com/u/xi...

掘金:https://juejin.im/user/593943...

博客匯總項(xiàng)目開(kāi)源地址:https://github.com/yangchong2... TabLayout項(xiàng)目開(kāi)源地址:https://github.com/yangchong2...

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/75035.html

相關(guān)文章

  • TabLayout使用遇到的坑及方案

    摘要:但對(duì)于我們的對(duì)于界面還原度要求較高,對(duì)于之間的間距也有一些要求,所以也要處理,對(duì)于間距部分的處理可以按照之前的方式通過(guò)反射來(lái)完成。注意,這種方式因?yàn)樾枰?jì)算的文字寬度,所以要放到設(shè)置完所有的后調(diào)用。 修改下劃線(xiàn)寬度的坑 效果如下: showImg(https://s2.ax1x.com/2019/04/18/ES2KYV.png); 代碼實(shí)現(xiàn)方式: 如果想要實(shí)現(xiàn)這種效果,最主要控制的就...

    baishancloud 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<