仿知乎首页学习CoordinateLayout

作者: IAM四十二 | 来源:发表于2016-09-24 22:56 被阅读3650次

    前言###

    最近一段时间经常上知乎APP看各种神回复,发现其内部的滑动动画挺有意思,就研究了一下。并使用CoordinateLayout模仿了一下,效果如下。

    <img src="http:https://img.haomeiwen.com/i1115031/9e3d0fed7a99b56e.gif?imageMogr2/auto-orient/strip" >

    <img src="http:https://img.haomeiwen.com/i1115031/567f3faeae2416d4.gif?imageMogr2/auto-orient/strip" >

    <img src="http:https://img.haomeiwen.com/i1115031/c5c0bb20a80c63e1.gif?imageMogr2/auto-orient/strip" >

    <img src="http:https://img.haomeiwen.com/i1115031/4d99e09568eb2eae.gif?imageMogr2/auto-orient/strip" >

    这里用到图片及IOCN均来自于网络搜索。

    手机截取gif貌似永远都是这样模糊不清,有兴趣的同学可以点github查看源码。

    综述###

    CoordinateLayout是Android Design Support Library提供的一种布局方式。

    查看源码我们可以看到 CoordinateLayout继承自ViewGroup,是一个“超级强大”的FrameLayout,FrameLayout 相信大家都很熟悉,使用也很简单,FrameLayout可以说是让Android布局中有了“”的概念,那么这个CoordinateLayout又有什么神奇之处呢,下面我们就学习一下。

    Coordinate 按照字面意思理解,就是协调。它可以方便的实现布局内view协调

    那么究竟是怎么个调节法呢,我们来看一下。

    CoordinateLayout 使用###

    结合Snackbar####

    关于CoordinateLayout最经典的例子就是其结合Snackbar的使用了。

    <?xml version="1.0" encoding="utf-8"?>
    <android.support.design.widget.CoordinatorLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
    
        <android.support.design.widget.FloatingActionButton
            android:id="@+id/fab"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="end|bottom"
            android:layout_margin="16dp"
            android:src="@drawable/ic_done" />
    
    </android.support.design.widget.CoordinatorLayout>
    
    
    @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            findViewById(R.id.fab).setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
    
                    Snackbar.make(view,"FAB",Snackbar.LENGTH_LONG)
                            .setAction("cancel", new View.OnClickListener() {
                                @Override
                                public void onClick(View v) {
                                    //这里的单击事件代表点击消除Action后的响应事件
    
                                }
                            })
                            .show();
                }
            });
        }
    
    

    这里实现的效果就是如上图中message中那样,一个底部弹出的view。

    这就是所谓的协调,协调FloatingActionButton上移,不被顶部弹出的view所遮挡。

    这里如果没有使用CoordinateLayout作为根布局,而是使用LinearLayout或RelativeLayout等,那么FloatingActionButton将会被底部弹出的Snackbar所遮挡。

    结合AppBarLayout使用####

    说到CoordinateLayout就不得不提这个AppBarLayout,他们俩简直就是天生一对,二者结合使用,那画面真是太美了,想想都觉得刺激。

    这里看一下我们模仿首页顶部搜索栏的代码:

    <?xml version="1.0" encoding="utf-8"?>
    <android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
    
        xmlns:tools="http://schemas.android.com/tools"
        android:id="@+id/coordinatorLayout"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".fragments.IndexFragment">
    
        <android.support.design.widget.AppBarLayout
            android:id="@+id/index_app_bar"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:theme="@style/AppTheme.AppBarOverlay">
    
            <RelativeLayout
                android:layout_width="match_parent"
                android:layout_height="?attr/actionBarSize"
                android:background="@color/colorPrimary"
                app:layout_scrollFlags="scroll|enterAlways">
    
                <ImageView
                    android:id="@+id/live"
                    android:layout_width="24dp"
                    android:layout_height="24dp"
                    android:layout_alignParentRight="true"
                    android:layout_centerVertical="true"
                    android:layout_marginRight="5dp"
                    android:src="@drawable/live_button" />
    
                <RelativeLayout
                    android:layout_width="match_parent"
                    android:layout_height="match_parent"
                    android:layout_margin="10dp"
                    android:layout_toLeftOf="@id/live"
                    android:background="@color/searchmenu">
    
                    <ImageView
                        android:id="@+id/search"
                        android:layout_width="24dp"
                        android:layout_height="24dp"
                        android:layout_centerVertical="true"
                        android:layout_marginLeft="10dp"
                        android:src="@drawable/ic_search" />
    
                    <TextView
                        android:layout_width="match_parent"
                        android:layout_height="wrap_content"
                        android:layout_centerVertical="true"
                        android:layout_marginLeft="10dp"
                        android:layout_toRightOf="@id/search"
                        android:text="搜索话题、问题或人"
                        android:textSize="16sp" />
    
                </RelativeLayout>
            </RelativeLayout>
        </android.support.design.widget.AppBarLayout>
    
            .......
    
    </android.support.design.widget.CoordinatorLayout>
    
    

    这里我们在AppBarLayout内部嵌套了一个RelativeLayout,在这个RelativeLayout中我们模仿了顶部的搜索栏的布局效果,这个很简单。这里最核心的东西就是

    app:layout_scrollFlags="scroll|enterAlways"
    

    这行代码。什么意思呢?这个app:layout_scrollFlags有下面几个值:

    • scroll: 所有想滚动出屏幕的view都需要设置这个flag, 没有设置这个flag的view将被固定在屏幕顶部。

    • enterAlways: 设置这个flag时,向下的滚动都会导致该view变为可见。

    • enterAlwaysCollapsed: 当你的视图已经设置minHeight属性又使用此标志时,你的视图只能已最小高度进入,只有当滚动视图到达顶部时才扩大到完整高度。

    • exitUntilCollapsed: 滚动退出屏幕,最后折叠在顶端。

    • snap: 视图在滚动时会有一种“就近原则”,怎么说呢,就是当视图展开时,如果滑动中展 开的内容超过视图的75%,那么视图依旧会保持展开;当视图处于关闭时,如果滑动中展开的部分小于视图的25%,那么视图会保持关闭。总的来说,就是会让动画有一种弹性的视觉效果。

    这里我们使用了scroll 和 enterAlways ,就很容易的实现了向下滑动时顶部隐藏,向下滑动时顶部出现的效果。

    结合TabLayout使用####

    注意,这里所说的TabLayout是android.support.design.widget.TabLayout,不是很久以前的那个TabLayout。

    这里我们模仿的时候,用于种种原因没能使用TabLayout的动态效果,只是简单的结合ViewPager使用了一下,第二个页面discovery就是使用TabLayout作为顶部的Indicator使用,实际中使用这个TabLayout还可以产生一种滑动时tab 悬停的效果。

    结合CollapsingToolbarLayout使用####

    个人感觉,这是整个CoordinateLayout中最拉风的动画特效,主要是实现一种“折叠”的动画效果。我们在模仿个人中心的时候就是用到了这个功能:

    看一下个人中心的布局文件:

    <?xml version="1.0" encoding="utf-8"?>
    <android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
    
        <android.support.design.widget.AppBarLayout
            android:id="@+id/appbar"
            android:layout_width="match_parent"
            android:layout_height="256dp"
            android:fitsSystemWindows="true"
            android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar">
    
            <android.support.design.widget.CollapsingToolbarLayout
                android:id="@+id/collapsing_toolbar"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:fitsSystemWindows="true"
                app:contentScrim="?attr/colorPrimary"
                app:expandedTitleMarginEnd="64dp"
                app:expandedTitleMarginStart="48dp"
                app:layout_scrollFlags="scroll|exitUntilCollapsed|snap">
    
                <RelativeLayout
                    android:layout_width="match_parent"
                    android:layout_height="match_parent"
                    android:background="@drawable/user_bg"
                    app:layout_collapseMode="parallax">
    
                    <de.hdodenhof.circleimageview.CircleImageView
                        android:layout_width="68dp"
                        android:layout_height="68dp"
                        android:layout_centerInParent="true"
                        android:src="@drawable/profile" />
    
                </RelativeLayout>
    
                <android.support.v7.widget.Toolbar
                    android:id="@+id/toolbar"
                    android:layout_width="match_parent"
                    android:layout_height="?attr/actionBarSize"
                    app:layout_collapseMode="pin"
                    app:popupTheme="@style/ThemeOverlay.AppCompat.Light" />
    
            </android.support.design.widget.CollapsingToolbarLayout>
    
        </android.support.design.widget.AppBarLayout>
    
        <android.support.v4.widget.NestedScrollView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            app:layout_behavior="@string/appbar_scrolling_view_behavior">
    
            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:orientation="vertical"
                android:paddingTop="10dp">
    
                <ImageView
                    android:layout_width="match_parent"
                    android:layout_height="match_parent"
                    android:scaleType="fitStart"
                    android:src="@drawable/fake" />
    
            </LinearLayout>
    
        </android.support.v4.widget.NestedScrollView>
    
        <android.support.design.widget.FloatingActionButton
            android:id="@+id/btn"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_margin="16dp"
            android:clickable="true"
            android:src="@drawable/ic_edit"
            app:layout_anchor="@id/appbar"
            app:layout_anchorGravity="bottom|right|end" />
    
    </android.support.design.widget.CoordinatorLayout>
    
    • 首先,我们在AppBarLayout中嵌套一个CollapsingToolbarLayout,并指定其
    app:layout_scrollFlags="scroll|exitUntilCollapsed|snap"
    

    这个属性前面介绍过了,这里三种属性结合就可实现滚动中“折叠视差”的效果了。

    • 接下来,我们又在CollapsingToolbarLayout放置了一个RelativeLayout。这个RelativeLayout有一个很重要的设置;
    app:layout_collapseMode="parallax"
    

    这个layout_collapseMode就是用来设置整个RelativeLayout的折叠效果的,有两种取值,“pin”:固定模式,在折叠的时候最后固定在顶端;“parallax”:视差模式,在折叠的时候会有个视差折叠的效果。

    • 最后是Toolbar,可以看到Toolbar的collapseMode设置为pin,这样向上滑动时,当RelativeLayout的内容完全折叠后,Toolbar将显示在顶部;而向下滑动时,Toolbar将消失,而RelativeLayout的内容会动态的折叠展开,而且由于设置了snap,会有一种轻微的弹性效果。

    这里需要注意,这个时候,我们需要将AppBarLayout的高度设置为固定值

    CoordinatorLayout 还提供了一个 layout_anchor 的属性,连同 layout_anchorGravity 一起,可以用来放置与其他视图关联在一起的悬浮视图(如 FloatingActionButton)。

    这里如果我们将floatingActionButton设置为:

    android:layout_gravity="bottom|right|end"
    

    FloatingActionButton将位于整个屏幕的右下角。

    使用细节###

    这里需要注意的是,使用AppBarLayout时,为了实现其滚动时的效果,在其下面必须有一个可滚动的View,并且需要为其设置app:layout_behavior属性。

    比如我们在结合结合CollapsingToolbarLayout使用时,
    在AppBarLayout的下面放置了一个NestedScrollView,并设置其app:layout_behavior="@string/appbar_scrolling_view_behavior"。

    而在其他页面,我们AppBarLayout的下面放置了ViewPager或者是FrameLayout都设置了相应的属性;具体可参考源码。

    Behavior###

    上面我们提到了layout_behavior,这是个什么意思呢?

    这里就不得不说这个Behavior了,可以说Behavior是整个CoordinateLayout最核心的东西。还记得我们最开始的列子吗?FloatingActionButton会随着Snackbar的出现,自动的调节自己的位置,这是怎样的实现的呢?

    我们通过追踪查看 Snackbar 的 show() 这个方法,最终会在Snack的源码中找到如下实现:

    final void showView() {
            if (mView.getParent() == null) {
                final ViewGroup.LayoutParams lp = mView.getLayoutParams();
    
                if (lp instanceof CoordinatorLayout.LayoutParams) {
                    // If our LayoutParams are from a CoordinatorLayout, we'll setup our Behavior
                    final CoordinatorLayout.LayoutParams clp = (CoordinatorLayout.LayoutParams) lp;
    
                    final Behavior behavior = new Behavior();
                    behavior.setStartAlphaSwipeDistance(0.1f);
                    behavior.setEndAlphaSwipeDistance(0.6f);
                    behavior.setSwipeDirection(SwipeDismissBehavior.SWIPE_DIRECTION_START_TO_END);
                    behavior.setListener(new SwipeDismissBehavior.OnDismissListener() {
                        @Override
                        public void onDismiss(View view) {
                            view.setVisibility(View.GONE);
                            dispatchDismiss(Callback.DISMISS_EVENT_SWIPE);
                        }
    
                        @Override
                        public void onDragStateChanged(int state) {
                            switch (state) {
                                case SwipeDismissBehavior.STATE_DRAGGING:
                                case SwipeDismissBehavior.STATE_SETTLING:
                                    // If the view is being dragged or settling, cancel the timeout
                                    SnackbarManager.getInstance().cancelTimeout(mManagerCallback);
                                    break;
                                case SwipeDismissBehavior.STATE_IDLE:
                                    // If the view has been released and is idle, restore the timeout
                                    SnackbarManager.getInstance().restoreTimeout(mManagerCallback);
                                    break;
                            }
                        }
                    });
                    clp.setBehavior(behavior);
                    // Also set the inset edge so that views can dodge the snackbar correctly
                    clp.insetEdge = Gravity.BOTTOM;
                }
    
                mTargetParent.addView(mView);
            }
    
            
        ......
            
        }
    

    我们可以看到,当Snack执行show方法的时候,会生成一个Behavior对象,然后将这个对象set给CoordinateLayout,而CoordinateLayout会根据这个Behavior执行动作。这个方法下面省略的大体上就是一个Translation属性动画的实现,这里就不展开来说了。

    回到我们之前所说,我们需要为带有滚动属性的view设置layout_behavior这个属性,我们为其设置的值

    app:layout_behavior="@string/appbar_scrolling_view_behavior"
    
    <string name="appbar_scrolling_view_behavior" translatable="false">android.support.design.widget.AppBarLayout$ScrollingViewBehavior</string>
    

    我们可以在AppBarLayout的源码中找到这个ScrollingViewBehavior,其最终也是继承自Behavior实现了特定的效果。

    现在或许你有疑问,这个神秘的Behavior到底是个什么鬼?

    Behavior 核心概念####

    Behavior 就是行为,他定义了View的行为。

    CoordinatorLayout的工作原理是搜索定义了CoordinatorLayout Behavior 的子view,不管是通过在xml中使用app:layout_behavior标签还是通过在代码中对view类使用@DefaultBehavior修饰符来添加注解。当滚动发生的时候,CoordinatorLayout会尝试触发那些声明了依赖的子view。

    Behavior最基础的两个方法是:layoutDependsOn() 和onDependentViewChanged();这两个方法的说明如下:

     public boolean layoutDependsOn(CoordinatorLayout parent, T child, 
                                                View dependency) {
        boolean result;
        //返回false表示child不依赖dependency,ture表示依赖
        return result;    
    }
    
    
    /**
    * 当dependency发生改变时(位置、宽高等),执行这个函数
    * 返回true表示child的位置或者是宽高要发生改变,否则就返回false
    */
    @Override
    public boolean onDependentViewChanged(CoordinatorLayout parent, T child, View dependency) {
         //child要执行的具体动作
            return true;
    }
    

    FloatingActionButton 的Behavior####

    我们用Android Studio查看FloatingActionButton的源码,会发现他包含了一个Behavior的注解:

    @CoordinatorLayout.DefaultBehavior(FloatingActionButton.Behavior.class)
    public class FloatingActionButton extends VisibilityAwareImageButton {
    .....
    }
    

    这里我们看一下FloatingActionButton的注解参数FloatingActionButton.Behavior.class 是怎样实现的。通过源码我们发现这个FloatingActionButton的Behavior继承自CoordinateLayout的Behavior,而且只实现了onDependentViewChanged方法,我们看一下:

    public static class Behavior extends CoordinatorLayout.Behavior<FloatingActionButton> {
            private static final boolean AUTO_HIDE_DEFAULT = true;
    
            private Rect mTmpRect;
            private OnVisibilityChangedListener mInternalAutoHideListener;
            private boolean mAutoHideEnabled;
    
            public Behavior() {
                super();
                mAutoHideEnabled = AUTO_HIDE_DEFAULT;
            }
    
            public Behavior(Context context, AttributeSet attrs) {
                super(context, attrs);
                TypedArray a = context.obtainStyledAttributes(attrs,
                        R.styleable.FloatingActionButton_Behavior_Layout);
                mAutoHideEnabled = a.getBoolean(
                        R.styleable.FloatingActionButton_Behavior_Layout_behavior_autoHide,
                        AUTO_HIDE_DEFAULT);
                a.recycle();
            }
    
            @Override
            public boolean onDependentViewChanged(CoordinatorLayout parent, FloatingActionButton child,
                    View dependency) {
                if (dependency instanceof AppBarLayout) {
                    // If we're depending on an AppBarLayout we will show/hide it automatically
                    // if the FAB is anchored to the AppBarLayout
                    updateFabVisibilityForAppBarLayout(parent, (AppBarLayout) dependency, child);
                } else if (isBottomSheet(dependency)) {
                    updateFabVisibilityForBottomSheet(dependency, child);
                }
                return false;
            }
    
          
        }
    

    可以看到他在onDependentViewChanged中直接判断了当前依赖的view。我们在模仿个人中心时,设置的FloatingActionButton的dependency就是AppBarLayout。而在这个方法中,他就会根据此执行特定的操作,也就是updateFabVisibilityForAppBarLayout 这个方法中的内容。

    自定义Behavior####

    好了,说了这么多,下面我们说一下自定义Behavior。我们在模仿知乎底部用于切换Fragment的Tab时,便使用了一个自定义的Behavior。

    BottomViewBehavior
    public class BottomViewBehavior extends CoordinatorLayout.Behavior<View> {
        public BottomViewBehavior(Context context, AttributeSet attrs) {
            super(context, attrs);
        }
    
        @Override
        public boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency) {
            return dependency instanceof AppBarLayout;
        }
    
        @Override
        public boolean onDependentViewChanged(CoordinatorLayout parent, View child, View dependency) {
            float translationY = Math.abs(dependency.getTop());
            child.setTranslationY(translationY);
            return true;
        }
    
    }
    

    这里我们的思路很简单,就是我们的View 要依赖于顶部的AppBarLayout,而用其距离屏幕的距离,作为底部(tab)相对于屏幕的距离,这样当顶部的AppBarLayout 滑动出屏幕时,底部也将做相应的位移,当然这里底部tab 的高度是需要做限制的,不能大于顶部AppBarLayout的高度。

    <LinearLayout
            android:id="@+id/bottom"
            android:layout_width="match_parent"
            android:layout_height="48dp"
            android:layout_gravity="bottom"
            android:background="@color/white"
            android:orientation="horizontal"  
          app:layout_behavior="home.smart.fly.zhihuindex.behavior.BottomViewBehavior">
    
            <RadioGroup
                android:id="@+id/tabs_rg"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:orientation="horizontal"
                android:paddingLeft="5dp"
                android:paddingRight="5dp">
    
                <RadioButton
                    android:id="@+id/home_tab"
                    android:layout_width="0dp"
                    android:layout_height="match_parent"
                    android:layout_weight="1"
                    android:background="#00000000"
                    android:button="@null"
                    android:checked="true"
                    android:drawableTop="@drawable/home_sel"
                    android:gravity="center|bottom"
                    android:paddingTop="5dp" />
    
                <RadioButton
                    android:id="@+id/explore_tab"
                    android:layout_width="0dp"
                    android:layout_height="match_parent"
                    android:layout_weight="1"
                    android:background="#00000000"
                    android:button="@null"
                    android:drawableTop="@drawable/explore_sel"
                    android:gravity="center|bottom"
                    android:paddingTop="5dp" />
    
                <RadioButton
                    android:id="@+id/notify_tab"
                    android:layout_width="0dp"
                    android:layout_height="match_parent"
                    android:layout_weight="1"
                    android:background="#00000000"
                    android:button="@null"
                    android:drawableTop="@drawable/notify_sel"
                    android:gravity="center|bottom"
                    android:paddingTop="5dp" />
    
                <RadioButton
                    android:id="@+id/user_tab"
                    android:layout_width="0dp"
                    android:layout_height="match_parent"
                    android:layout_weight="1"
                    android:background="#00000000"
                    android:button="@null"
                    android:drawableTop="@drawable/user_sel"
                    android:gravity="center|bottom"
                    android:paddingTop="5dp" />
            </RadioGroup>
    
        </LinearLayout>
    

    我们将自定义的Behavior设置为这个bottom的app:layout_behavior就可以实现类似于知乎首页的那种效果了。

    FabBehavior

    这里我们用到的FloatingActionButton也可以自定义Behavior。

    public class FabBehavior extends CoordinatorLayout.Behavior<View> {
        private static final Interpolator INTERPOLATOR = new FastOutSlowInInterpolator();
        /**
         * 控件距离coordinatorLayout底部距离
         */
        private float viewDistance;
        private boolean aninmating;
    
        public FabBehavior(Context context, AttributeSet attrs) {
            super(context, attrs);
        }
    
        @Override
        public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, View child, View directTargetChild, View target, int nestedScrollAxes) {
    
            if(child.getVisibility() == View.VISIBLE&& viewDistance ==0){
                //获取控件距离父布局(coordinatorLayout)底部距离
                viewDistance =coordinatorLayout.getHeight()-child.getY();
            }
    
            return (nestedScrollAxes & ViewCompat.SCROLL_AXIS_VERTICAL) != 0;//判断是否竖直滚动
        }
    
        @Override
        public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, View child, View target, int dx, int dy, int[] consumed) {
            //dy大于0是向上滚动 小于0是向下滚动
    
            if (dy >=0&&!aninmating &&child.getVisibility()==View.VISIBLE) {
                hide(child);
            } else if (dy <0&&!aninmating &&child.getVisibility()==View.GONE) {
                show(child);
            }
        }
    
        private void hide(final View view) {
            ViewPropertyAnimator animator = view.animate().translationY(viewDistance).setInterpolator(INTERPOLATOR).setDuration(200);
    
            animator.setListener(new Animator.AnimatorListener() {
                @Override
                public void onAnimationStart(Animator animator) {
                    aninmating =true;
                }
    
                @Override
                public void onAnimationEnd(Animator animator) {
                    view.setVisibility(View.GONE);
                    aninmating =false;
                }
    
                @Override
                public void onAnimationCancel(Animator animator) {
                    show(view);
                }
    
                @Override
                public void onAnimationRepeat(Animator animator) {
                }
            });
            animator.start();
        }
    
        private void show(final View view) {
            ViewPropertyAnimator animator = view.animate().translationY(0).setInterpolator(INTERPOLATOR).setDuration(200);
            animator.setListener(new Animator.AnimatorListener() {
                @Override
                public void onAnimationStart(Animator animator) {
                    view.setVisibility(View.VISIBLE);
                    aninmating =true;
                }
    
                @Override
                public void onAnimationEnd(Animator animator) {
                    aninmating =false;
                }
    
                @Override
                public void onAnimationCancel(Animator animator) {
                    hide(view);
                }
    
                @Override
                public void onAnimationRepeat(Animator animator) {
                }
            });
            animator.start();
        }
    }
    

    这里我们并没有去实现layoutDependsOn() 和onDependentViewChanged()这两个方法,前面我们看FloatingActionButton源码的时候知道他已经实现了onDependentViewChanged,我们这里我们从自身需求出发,就其滑动时的特性,做了一个滑动屏幕时FloatingActionButton快速从底部弹出或隐藏的Behavior。结合注释,代码很容易理解。

    好了,这就是所有关于CoordinateLayout的东西了,可以看到Behavior是这个控件的核心,也是最难理解的东西。自定义Behavior可以让我们的滑动动画有无限的可能。

    总结###

    关于这个模仿知乎首页的实现,最初真的只是想研究一下“首页”是怎么实现的。结果随着Demo的展开,加上轻微强迫症的作祟,便成了现在这个样子。

    到这里,不得不说一下,个人感觉,真正的知乎首页应该是没有使用CoordinateLayout;因为按现在这种Activity+n*Fragment 的套路,使用CoordinateLayout完全是给自己添乱,因为CoordinateLayout是滑动特性是无法嵌套使用的(或者说很复杂,我没发现),当我在最外层的Activity中使用了CoordinateLayout后,内部的Fragment中再次使用CoordinateLayout时,就会发生意想不到的各种bug,所以你会发现我们模拟的个人中心是有问题的,这里就是嵌套CoordinateLayout后外部的CoordinateLayout失效了,导致底部的Behavior也失效。

    不过在整个模仿的过程,也算是对CoordinateLayout的一次深入了解吧,顺便也对SwipeRefreshLayout的内容和Tween Animation的使用做了一次巩固。首页RecycleView item中仿照Toolbar的弹出菜单,真的是耗费了不少时间。

    源码地址

    好了,按照惯例再次给出github地址。


    相关文章

      网友评论

      • 我不是死胖子:知乎是只有MainActivity的底部TabLayout和FLoatingActionBar上了Behavior.
        然后首页回答的ListFragment,用的是外部控制, 它顶部Toolbar的隐藏效果靠最外层的FrameInterceptLayout 的 dispatchTouchEvent 传递.
        最后是首页回答详情的DetailFragment(就是点进去的那个), 用的是内部控制, 是github上的https://github.com/ksoichiro/Android-ObservableScrollView 这个项目的ObservableWebView(对,知乎的回答详情内容是个webview), 把touch传递给了外部的View. 来隐藏头的toolbar和底部的点赞评论按钮.
        具体的我自己也写了个,http://www.jianshu.com/p/788edf302b43
        不过demo 还没抽出来, 等好了再补个地址.
        我不是死胖子:github链接: https://github.com/sunxlfred/FindZhihu
      • 海边的卡夫卡Fu:你好我想问下在个人主页那里,下面可以加viewpager么,现在的知乎是那样的
        IAM四十二:@海边的卡夫卡Fu 可以啊,TabLayout + ViewPager 完全没问题
      • tigerkint:可不可以隐藏的是任意view比如一个嵌套过后的linearlayout?
      • 903a60dc36bd:那个我下载了demo,自己照着demo做ui的时候,activity_main.xml中的radiobutton图标怎么也不出你的效果,图标显示过大。。。我复制你的xml问题过去还是这样,不知道你是在那里设置radiobutton 图标的大小,还是我项目的style没复制你的style而出现的问题?
        IAM四十二:@NidMo :wink: ,别客气
        903a60dc36bd:@IAM四十二 这样啊,我试试,麻烦了
        IAM四十二:这个可能还是和你用的radiobutton的图标大小和放置位置有关系,我代码里的那几个图标是96x96px大小,放置在了xxhdpi 这个目录下,Android 系统会对不同文件夹下的图片资源做处理;这个demo也没有做适配测试,因此,如果你自己的UI不合适,可以自己调整大小,和style没关系。
      • fendo:第一张图片的那个,+号按钮,叫什么?
        IAM四十二: @夏霂熠雨 不是github 地址吗?🤔🤔
        夏霂熠雨:你好,请问有完整的demo吗。
        IAM四十二:@fendo floating action button

      本文标题:仿知乎首页学习CoordinateLayout

      本文链接:https://www.haomeiwen.com/subject/yhaxyttx.html