Material Design之CoordinatorLayout实现滑动折叠

        前段时间PM提了一个需求,实现一种滑动折叠效果,这种效果与掌上英雄联盟的战绩页面很像。经过调研,发现使用CoordinatorLayout+AppBarLayout+CollapsingToolbarLayout可以轻松实现这一效果。先给大家看一下掌上英雄联盟的效果,大家可以先确定是不是自己想要的效果(PS:这战绩真不是我打的,我发誓。。。)。

一.布局文件

        布局文件详细代码如下所示,这里说几个注意点:

        (1)CoordinatorLayout必须作为最外层的布局。

        (2)AppBarLayout作为CoordinatorLayout的直接子布局。

        (3)CollapsingToolbarLayout作为AppBarLayout的直接子布局,CollapsingToolbarLayout包裹的内容是可以收缩和伸展的。

        (4)CollapsingToolbarLayout下方放了一个TabLayout,也是被AppBarLayout包裹的。

        (5)AppBarLayout的下方放一个ViewPager用来和TabLayout联动。




    

        

            

                

                


            


            

                

                    

                    

                    

                    


                    

                    

                

            

        

        

            

            

            
        

    

    

    

二.关键代码
1.ViewPager和TabLayout联动的实现

        首先,是ViewPager和TabLayout联动的实现,关键代码:mTabLayout.setupWithViewPager(mViewPager)。

        fragmentList = new ArrayList<>();
        fragment1 = new Fragment1();
        fragment2= new Fragment1();
        fragment3 = new Fragment1();
        fragmentList.add(fragment1);
        fragmentList.add(fragment2);
        fragmentList.add(fragment3);

        mViewPager.setAdapter(new FragmentStatePagerAdapter(getSupportFragmentManager()) {
            @Override
            public Fragment getItem(int i) {
                return fragmentList.get(i);
            }

            @Override
            public int getCount() {
                return fragmentList.size();
            }
        });
        mTabLayout.setupWithViewPager(mViewPager);
        mTabLayout.getTabAt(0).setText("战绩");
        mTabLayout.getTabAt(1).setText("能力");
        mTabLayout.getTabAt(2).setText("资产");

        或许,有的同学发现了,我已经在xml文件中写死了三个TabItem,为何又手动设置了一次。这里要特别注意,在调用setupWithViewPager后,需要手动的再设置一下TabItem的文本,不然不显示。为什么呢,我们可以看一下setupWithViewPager这个方法。来,一起跟踪一下TabLayout的源码:

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;
    }

        上面的源码,大都是与监听器有关的代码,最后,有一行设置PagerAdapter的代码:

this.setPagerAdapter((PagerAdapter)null, false);

        我们看到Adapter,都知道Adapter是View绑定数据的一个适配器。View没显示数据,肯定与Adapter有关系,我们继续跟踪:     

void setPagerAdapter(@Nullable PagerAdapter adapter, boolean addObserver) {
        if (this.pagerAdapter != null && this.pagerAdapterObserver != null) {
            this.pagerAdapter.unregisterDataSetObserver(this.pagerAdapterObserver);
        }

        this.pagerAdapter = adapter;
        if (addObserver && adapter != null) {
            if (this.pagerAdapterObserver == null) {
                this.pagerAdapterObserver = new TabLayout.PagerAdapterObserver();
            }

            adapter.registerDataSetObserver(this.pagerAdapterObserver);
        }

        this.populateFromPagerAdapter();
    }

        上面是注册和注销DataSetObserver,看名字我们能猜到是监听数据变化的一个观察者模式,与数据的展示无关,于是,我们跟踪最后那个不熟悉的populateFrompagerAdapter()方法:

    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));
                }
            }
        }

    }

        好了,我们找到问题根源了。在这个方法里面,调用了removeAllTabs,我们可以不用跟踪了,这个方法很明显,移除了所有的Tab,因此,我们原来设置的tab不显示了。

2.自定义AppBarStateChangeListener

        这个可以随意的,可以用AppBarLayout.OnOffsetChangedListener来判断AppBar的状态,也可以仿照这个去实现一个自定义的状态监听器:

public abstract class AppBarStateChangeListener implements AppBarLayout.OnOffsetChangedListener {
    public enum State {
        EXPANDED,
        COLLAPSED,
        IDLE
    }

    private State mCurrentState = State.IDLE;

    @Override
    public final void onOffsetChanged(AppBarLayout appBarLayout, int i) {
        if (i == 0) {
            if (mCurrentState != State.EXPANDED) {
                onStateChanged(appBarLayout, State.EXPANDED);
            }
            mCurrentState = State.EXPANDED;
        } else if (Math.abs(i) >= appBarLayout.getTotalScrollRange()) {
            if (mCurrentState != State.COLLAPSED) {
                onStateChanged(appBarLayout, State.COLLAPSED);
            }
            mCurrentState = State.COLLAPSED;
        } else {
            if (mCurrentState != State.IDLE) {
                onStateChanged(appBarLayout, State.IDLE);
            }
            mCurrentState = State.IDLE;
        }
    }

    public abstract void onStateChanged(AppBarLayout appBarLayout, State state);
}

3.动态显示和隐藏顶部的元素

        有了收缩和伸展状态的监听器,我们就可以根据状态来动态的显示和隐藏顶部的控件了,代码如下:

    private void changeToolBarState(AppBarStateChangeListener.State state) {

        if (state == AppBarStateChangeListener.State.EXPANDED) {

            mView.setVisibility(View.VISIBLE);
            mInnerSearch.setVisibility(View.VISIBLE);
            mEditText.setVisibility(View.VISIBLE);
            mAuthorSearch.setVisibility(View.GONE);
            mTopHead.setVisibility(View.GONE);

        } else if (state == AppBarStateChangeListener.State.COLLAPSED) {
            mView.setVisibility(View.GONE);
            mInnerSearch.setVisibility(View.GONE);
            mEditText.setVisibility(View.GONE);
            mAuthorSearch.setVisibility(View.VISIBLE);
            mTopHead.setVisibility(View.VISIBLE);
        }else{
            mView.setVisibility(View.VISIBLE);
            mInnerSearch.setVisibility(View.VISIBLE);
            mEditText.setVisibility(View.VISIBLE);
            mAuthorSearch.setVisibility(View.GONE);
            mTopHead.setVisibility(View.GONE);
        }

    }

三.最终效果

        好了,我们看一下最后的效果:

        最后,简单总结一下。Material Design库为我们提供了很多华丽的控件和效果,有些看似很复杂的效果,可能使用Material Design来实现会非常的简单。我本人也会继续学习和研究Material Design库其他的效果和控件,最后也会总结和分享。最后,附上Github源码,感兴趣的朋友可以自行查看:https://github.com/hearttudu/CollapsingToolbarLayoutDemo

你可能感兴趣的:(Material,Design)