仿知乎个人中心沉浸式页面

最终效果:




主要用到三个组件:CoordinatorLayoutAppBarLayoutCollapsingToolbarLayout

  • CoordinatorLayout

协调者布局,Google将其解释为一个超级FrameLayout

CoordinatorLayout is a super-powered FrameLayout.

CoordinatorLayout是支持包”com.Android.support:design”里很重要的一个控件,继承于FrameLayout


它有两种使用场景:

1.作为 一个应用顶层的装饰布局,也就是一个Activity Layout 的最外一层布局。

2.作为一个或多个有特定响应动作的容器,协调子控件的相互作用


CoordinatorLayout存在的意义就是通过自定义ChildrenBehaviors(行为)来实现控件之间的交互动画效果,我们在使用CoordinatorLayout时,也可以自定义Behavior来实现一些复杂的交互效果,Google 也给我们定义了一些常用的Behavior,如后面要用的到的 appbar_scrolling_view_behavior ,用于协调 AppBarLayout ScrollView 滑动的Behavior

这里首先提出一个概念, CoordinatorLayout 其实是将其下的所有子View都抽象成:


互相依赖(depends)的关系.


CoordinatorLayout的使用核心是BehaviorBehavior就是执行你定制的动作。在讲Behavior之前必须先理解两个概念:ChildDependency

Child是指要执行动作的CoordinatorLayoutView。而Dependency是指Child依赖的View


因此某个view可以基于另一个view来定位, 但这只是冰山一角, 这样抽象的好处更强大的地方在于:每一个view的所有属性, 坐标, 样式, 状态等一切都可以依赖于另一个view, 因此使得parentView和所有childView之间都可以互相联动起来.

举个栗子:



    

    


可以看出这里的关键在于 layout_anchor layout_anchorGravity 两个参数, 意思也很简单:

就是这个View以指定的 anchor 作为参照物来定位, anchorGravity 设置为 bottom|right 则表示将FloatingActionButton放置于参照物(FrameLayout)的右下角.由此实现了view之间的联动。


  • AppBarLayout

AppBarLayout是一种支持响应滚动手势app bar布局,继承自LinearLayout,布局方向为垂直方向。

AppBarLayout是在LinearLayou上加了一些材料设计的概念,它可以让你定制当某个可滚动View的滚动手势发生变化时,其内部的子View实现何种动作。

我们可以通过设置AppBarLayout子控件的layout_scrollFlags参数,来控制AppBarLayout中控件的行为。


layout_scrollFlags参数

  • scroll 和其他控件滑动联动的基础,下面的其他属性值,先要设置了scroll后才有效果。
  • enterAlways:当屏幕下滑时,设置了这个行为的控件(比如toolbar)就会立马滑回屏幕,类似于快速返回的效果,而且不管下面的滑动组件(比如ScrollView是否正在滑动)
  • enterAlwaysCollapsed:是enterAlways的附加选项,一般跟enterAlways一起使用。控件首先是enterAlways效果,但只能滑动minHeight的距离。直到下面的滑动组件(比如ScrollView完全展示时),控件才能继续向下滑动。
  • exitUntilCollapsed:向上滑动的时候这个控件会收缩,但最多只能收缩到控件设定的minHeight,实际能滚动的距离为(layout_height-minHeight)。
  • snap:这个属性让控件变得有弹性,如果控件(比如toolbar)显示了75%的高度,就会显示出来,如果只有25%显示,就会隐藏。


ViewPager设置行为,实现与AppBarLayout联动。

app:layout_behavior=“@string/appbar_scrolling_view_behavior”


举个栗子:




    


AppBarLayout的子控件toolbar中设置滑动属性layout_scrollFlags=“scroll”toolbar会跟随滑动事件一起发生移动。

  • CollapsingToolbarLayout


CollapsingToolbarLayout是用来对Toolbar进行再次包装的ViewGroup,主要是用于实现折叠App Bar效果,是专门用来实现子布局内不同元素响应滚动细节的布局。

它需要放在AppBarLayout布局里面,并且作为AppBarLayout直接子ViewCollapsingToolbarLayout主要包括几个功能:


(1) 折叠TitleCollapsing title:当布局内容全部显示出来时,title是最大的,但是随着View逐步移出屏幕顶部,title变得越来越小。你可以通过调用setTitle函数来设置title

(2)内容纱布(Content scrim:根据滚动的位置是否到达一个阀值,来决定是否对View“盖上纱布。可以通过setContentScrim(Drawable)来设置纱布的图片。

(3)状态栏纱布(Status bar scrim):根据滚动位置是否到达一个阀值决定是否对状态栏盖上纱布,你可以通过setStatusBarScrim(Drawable)来设置纱布图片,但是只能在LOLLIPOP设备上面有作用。

(4)视差滚动子View(Parallax scrolling children):View可以选择在当前的布局当时是否以视差的方式来跟随滚动。将布局参数app:layout_collapseMode设为parallax

(5)将子View位置固定(Pinned position children):子View可以选择是否在全局空间上固定位置,这对于Toolbar来说非常有用,因为当布局在移动时,可以将Toolbar固定位置而不受移动的影响。 app:layout_collapseMode设为pin

CollapsingToolbarLayout可以

通过app:contentScrim设置折叠时工具栏布局的颜色。

通过app:statusBarScrim设置折叠时状态栏的颜色。

默认contentScrimcolorPrimary的色值,statusBarScrimcolorPrimaryDark的色值。


CollapsingToolbarLayout的子布局有3种折叠模式(Toolbar中设置的app:layout_collapseMode

  • off:默认属性,布局将正常显示,没有折叠的行为。
  • pinCollapsingToolbarLayout折叠后,此布局将固定在顶部。
  • parallaxCollapsingToolbarLayout折叠时,此布局也会有视差折叠效果。


CollapsingToolbarLayout的子布局设置了parallax模式时,我们还可以通过app:layout_collapseParallaxMultiplier设置视差滚动因子,值为:0~1

举个栗子:





    


        

        

    



在此栗子中,AppBarLayout的直属子viewlayout_scrollflags设置为scroll | exitUtilCollapsed,表示collapsingtoolbarlayout有联动滑动效果且滑动minHeight的距离,再看其子viewImageview设置了layout_collapsemodeparallax,则实现视差折叠效果,而toolbar因设置了layout_collapsemodepin,会固定在顶部。


还有一些常用的属性及其含义


|app:title|设置标题|

|app:collapsedTitleGravity="center"|设置标题位置|

|app:contentScrim |设置折叠时toolbar的颜色,默认是colorPrimary的色值|

|app:statusBarScrim | 设置折叠时状态栏的颜色 ,默认是colorPrimaryDark的色值|

|app:layout_collapseParallaxMultiplier|设置视差|

|app:layout_collapseMode="parallax"| 视差模式,在折叠的时候会有个视差折叠的效果|

|app:layout_collapseMode="pin"|固定模式,在折叠的时候最后固定在顶端 |


当然也可以在java中设置。


到这里这三个重要组件的主要功能就总结完成了,接下来我们来看看这次要做的仿知乎个人中心页面。   


这是我们最终要实现的效果,其中几个重要的部分:


沉浸式statusbar,可滑动折叠imageviewtoolbar

Viewpagertablayoutfragmentrecyclerview的结合。

recyclerviewappbarlayout的联动。

tablayout上滑到toolbartoolbar合并。


  • 实现一 . 沉浸式statusbar,可滑动折叠imageviewtoolbar


1.沉浸式statusbar第一篇已经提过,这里也依然使用,略。


2.可滑动折叠的imageviewtoolbar就要用到我们讲过的三个组件了。


思路:


  • 顶层布局CoordinatorLayout,用来协调子控件间的相互作用。
  • 子布局AppBarLayout,用来支持imagerviewtoolbar的响应滚动手势。
  • 为了使其能有折叠效果,则需要CollapsingToolbarLayout作为AppBarLayout的直接子布局,设置CollapsingToolbarLayoutlayout_scrollFlags
  • 设置imageviewtoolbar的折叠属性layout_collapseMode分别为parallaxpin使其折叠或固定在顶部。


思路有了,布局也就写出来了。





    


        


            


            



        

                     。。。(不重要先省略)

    

                     。。。(不重要先省略)


配合javatoolbar等的设置,这一步就完成了。

  • 实现二 . Viewpagertablayoutfragmentrecyclerview的结合。


由于viewpager中要嵌套fragment,故我们使用FragmentPagerAdapter,整体还是比较容易的。


自定义FragmentPagerAdapter

public class ZhiHuFragmentAdapter extends FragmentPagerAdapter {


    private FragmentManager fragmentManager;

    private List fragmentList;

    private String[] titles;



    public ZhiHuFragmentAdapter(FragmentManager fm, List list, String[] titles) {

        super(fm);

        this.fragmentManager = fm;

        this.fragmentList = list;

        this.titles = titles;

    }


    @Override

    public Fragment getItem(int position) {

        return fragmentList.get(position);

    }

    @Override

    public int getCount() {

        return fragmentList.size();

    }

}

标题我们用tablayout来实现,那么tablayoutviewpager怎么实现联动呢:

mTabLayout.setupWithViewPager(mViewpager);


并且在FragmentPagerAdapter中要重写getpagetitle方法,titles是你的标题数据:

@Override

public CharSequence getPageTitle(int position) {

    return titles[position];

}

并在activity通过初始化fragmentadapter将需要的参数传进去(这里我为了简便,将同一个fragment放入fragmentlist中),并设置viewpageradapterfragmentadapter

private String[] titles = {"动态", "回答", "文章", "想法"};

fragmentList = new ArrayList<>();

for (int i = 0; i < titles.length; i++) {

    ListFragment fragment = new ListFragment();

    fragmentList.add(fragment);

}

ZhiHuFragmentAdapter adapter = new ZhiHuFragmentAdapter(

        getSupportFragmentManager(), fragmentList, titles);



mViewpager.setAdapter(adapter);


fragment中嵌套recyclerview,在自定义fragmentoncreateViewonActivityCreated方法中初始化recyclerview和其adapter(比较容易):

@Nullable

@Override

public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,

                         @Nullable Bundle savedInstanceState) {

    View view = inflater.inflate(R.layout.fragment_list, null);

    recyclerView = (RecyclerView) view.findViewById(R.id.recycler_view);

    return view;

}


@SuppressLint("ResourceAsColor")

@Override

public void onActivityCreated(@Nullable Bundle savedInstanceState) {

    super.onActivityCreated(savedInstanceState);



    TextAdapter adapter = new TextAdapter();

    recyclerView.setLayoutManager(new LinearLayoutManager(getContext()));

    recyclerView.setAdapter(adapter);

    recyclerView.setNestedScrollingEnabled(false);

}

自定义recyclerviewadapter(比较容易):

public class TextAdapter extends RecyclerView.Adapter {

    @Override

    public TextViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {

        View view = LayoutInflater.from(getContext()).inflate

                (R.layout.item_recyclerview, parent, false);

        return new TextViewHolder(view);

    }

    @Override

    public void onBindViewHolder(TextViewHolder holder, int position) {

        holder.mTextView.setText(position + "");

    }

    @Override

    public int getItemCount() {

        return 6;

    }

};

public class TextViewHolder extends RecyclerView.ViewHolder {

    private TextView mTextView;



    public TextViewHolder(View itemView) {

        super(itemView);

        mTextView = (TextView) itemView.findViewById(R.id.text_recycler_view);

    }

}


Listfragmentfor循环中产生实例加入fragmentlist中即可。

完成。


  • 实现三 . recyclerviewappbarlayout的联动。


toolbarimagerview的滑动和折叠效果完成后,在imageview处滑动会产生效果,但是由于我们下部分布局也是滑动的viewrecyclerview),所以为了用户体验,我们希望在下部分发生滑动手势时,toolbarimageview部分也能产生滑动效果,那怎么实现呢?就用appbarlayoutlayout_behavior属性来实现两个view的联动。


我们这里就用有着嵌套滑动机制的NestedScrollView,(嵌套联动:https://www.jianshu.com/p/1806ed9737f6)并设置


app:layout_behavior=“@string/appbar_scrolling_view_behavior”


联动完成,并将我们的textview布局和viewpager嵌套在NestedScrollView中,注意,因为NestedScrollView只能有一个子布局,所以textviewviewpager需要用relativelayoutlinearlayout等布局嵌套一下。



布局如下:(里面一些非重点的属性已经省略,加入scrollview是为了滑动更快)




    


        


            


            



        


        

    


  • 实现四 . tablayout上滑到toolbartoolbar合并。


这步当时思路:

1.CoordinatorLayoutAppBarLayoutCollapsingToolbarLayout三个组件来完成。首先,我们要完成的效果是tablayout上滑到toolbar时与toolbar融合,下滑经过toolbar后再分开。这三个组件能够实现这样的效果吗?其次,我们要完成的效果并不是toolbar下面就是tablayout,而是toolbar下方有一些textview内容,其次下方是tablayout,那么这时候就有一个问题,就是appbarlayoutcollapsingtoolbarlayout究竟能不能在两个不相邻的子控件上都产生效果?经过我的各种尝试,没有成功(也可能是能力不够,更深层次的自定义和各种属性不熟悉),所以我放弃了这种思路。


2.第二种思路,当tablayout上滑到toolbar时,toolbar原本下方有的另一个tablayout控件显示出来,当下滑时,再让其消失。这种方式还是可行的,可通过NestedScrollView的滑动监听来监听tablayout的坐标位置和toolbar底部的坐标位置的关系,判断(新)tablayout的显示和消失。


NestedScrollView的监听事件onScrollChangeListener()


Tablayout获取xy坐标,tablayout.getX(),getY()


tablayouty坐标小于toolbar底部y坐标,则让新tablayout显示,否则消失。


  • 遇到的问题一:tablayout.getY()获取不到实时坐标。
  • 解决方法:搜索一下发现getX()getY()方法获取的是View左上角相对于父容器的坐标,当View没有发生平移操作时,getX()==getLeft()getY==getTop(),所以我们需要 View.getLocationOnScreen(int[] position)来获取View在屏幕中的坐标。


代码如下:

mScrollView.setOnScrollChangeListener(new View.OnScrollChangeListener() {

    @Override

    public void onScrollChange(View view, int i, int i1, int i2, int i3) {

        int[] location = new int[2];

        mTabLayout.getLocationInWindow(location);

        mTabLayout.getLocationOnScreen(location);


        if (location[1] < Utils.dp2px(MainActivity.this, 88)) {

            mTabLayoutCopy.setVisibility(View.VISIBLE);

            mTabLayout.setVisibility(View.INVISIBLE);

        } else {

            mTabLayoutCopy.setVisibility(View.GONE);

            mTabLayout.setVisibility(View.VISIBLE);

        }

    }


  • 遇到的问题二:recyclerviewscrollview的焦点冲突。


测试时候发现下滑时顶部tablayout发生抖动,下滑失败,并且进入activityrecyclerview的布局充满屏幕,布局时从中间开始显示。


  • 解决方法:由于recyclerview是被fragmentviewpager嵌套的,所以将viewpager的父布局设为获得焦点,问题解决。

代码如下:

@Override

protected void onResume() {

    super.onResume();

    mRelativeLayout.setFocusable(true);

    mRelativeLayout.setFocusableInTouchMode(true);

    mRelativeLayout.requestFocus();

}

完整代码地址:http://download.csdn.net/download/denglixuan1996/10265021

你可能感兴趣的:(邓丽璇的个人文档)