Material Design系列,自定义Behavior实现Android知乎首页
Posted 严振杰
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Material Design系列,自定义Behavior实现Android知乎首页相关的知识,希望对你有一定的参考价值。
Material Design系列,自定义Behavior实现android知乎首页
版权声明:转载必须注明本文转自严振杰的博客: http://blog.csdn.net/yanzhenjie1003
友情连接:
Material Design博客专栏
系列博客:
1. Material Design系列,Behavior之BottomSheetBehavior与BottomSheetDialog
2. Material Design系列,Behavior之SwipeDismissBehavior
3. Material Design系列,自定义Behavior之上滑显示返回顶部按钮
4. Material Design系列,自定义Behavior实现Android知乎首页
5. Material Design系列,自定义Behavior支持所有 View
本博客目的:仿知乎首页向上滑动时动画隐藏Toolbar
、FlocationActionButton
、Tab导航
,下滑时显示,如果和你的期望不同,那么你可以不需要看了,免的浪费你的宝贵时间噢。
效果预览
知乎效果:
本博客实现效果:
今天效果的源代码下载链接在文章末尾。
实现分析
这个效果其实并不难实现,但是它的用处很大,当用户手指上滑,屏幕上显示下方内容的时候,隐藏Toolbar
、Tab导航
、FAB
来腾出更大的空间显示内容,让用户爽。简单粗暴,但这就是我们的目的。
首先就是头部的Toolbar
,这个就不用说了吧,基本会,不会的人随便看我一篇博客的demo都有这个效果,简直小学级别。
其次来看看FAB(FlocationActionButton)的显示和隐藏,知乎是用的平移,我们这里做个优化改动,当然平移也是可以的,如果你看过我的Material Design系列,自定义Behavior之上滑显示返回顶部按钮这篇博客的话。那么我们的FAB的动画隐藏和显示也是用上一篇博客的原理,没有看上一篇博客的同学需要回过头看看噢,这里不在赘述。
最后来看下面的Tab导航的隐藏和显示,这个确确实实用平移更好是吧,然而相信你如果看过我Material Design系列,Behavior之BottomSheetBehavior与BottomSheetDialog这篇博客的话,这个效果实现起来也不难。强烈建议看下文之前读这篇文章,不然真的没法继续看下去了。
其实代码量还是很少的,主要是Behavior
原理、Behavior
和CoordinatorLayout
如何结合使用。so,强烈建议去上读下上面两篇博客噢。
……
好的,五分钟过去了,我相信你大概已经速读了上面提到的两篇博客了。那么在第一篇FAB的那篇博客中实现的效果是手指向上滑时(屏幕显示下方的内容时)显示FAB用来回到顶部,但是这里刚好是相反的:向上滑时隐藏FAB。如果你认真读了原理解释的那一段或者运行过demo,这个效果so easy吧。第二篇博客中也讲到了如何隐藏和显示这个Tab导航
,那么有的同学就觉得今天的博客就结束了吧?答案当然是No了,不然我也不会再开一篇博客来讲这个了。
为什么呢?还是有难点的,难点在哪里?就是上面讲到的两个Behavior
如何和CoordinatorLayout
结合使用,同时实现两个效果。而且BottomSheetBehavior
隐藏和显示Tab导航
这个里面之前我们使用Button
来控制的,如何做到`CoordinatorLayout
中的ContentView
滑动时动态的显示和隐藏Tab导航
呢?
撸码
闲扯装逼都弄完了,下来来点真材实料,带领大家一起代码撸起来。
页面布局
上面的引文和介绍,我们已经知道了FAB的显示和隐藏用自定义Behavior
实现,Tab导航
用BottomSheetBehavior
来实现,那么我们布局文件也该问世了:
<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:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="@style/AppTheme.AppBarOverlay">
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:layout_scrollFlags="scroll|enterAlways|snap"
app:popupTheme="@style/AppTheme.PopupOverlay" />
</android.support.design.widget.AppBarLayout>
<android.support.v7.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
<LinearLayout
android:id="@+id/tab_layout"
android:layout_width="match_parent"
android:layout_height="?actionBarSize"
android:layout_alignParentBottom="true"
android:background="@android:color/white"
app:layout_behavior="@string/bottom_sheet_behavior">
<Button
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:text="第一" />
<Button
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:text="第二" />
<Button
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:text="第三" />
<Button
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:text="第四" />
</LinearLayout>
<android.support.design.widget.FloatingActionButton
android:id="@+id/fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_marginBottom="70dp"
android:layout_marginEnd="16dp"
android:layout_marginRight="16dp"
android:src="@mipmap/ic_action_new"
app:layout_behavior="@string/scale_down_show_behavior"
app:layout_scrollFlags="scroll|enterAlways|snap" />
</android.support.design.widget.CoordinatorLayout>
还是稍微解释下,内容区域是一个RecyclerView
,使用的Behavior
是design的ScrollingViewBehavior
:
app:layout_behavior="@string/appbar_scrolling_view_behavior"
然后一个LinearLayout
,使用的Behavior
是design的BottomSheetBehavior
:
app:layout_behavior="@string/bottom_sheet_behavior"
最后一个FloatingActionButton
,使用我们的自定义ScaleDownShowBehavior
:
app:layout_behavior="@string/scale_down_show_behavior"
其他两个都是design自带的,唯有FloatingActionButton
的ScaleDownShowBehavior
需要我们自定义,那么下面我们就来实现下ScaleDownShowBehavior
。
自定义Behavior实现FAB的动画控制
这里又谈到了自定义Behavior
了,首先就来实现:用户手指在屏幕上滑,隐藏FAB,留出更多位置给用户。
这里还是继承FloatingActionButton.Behavior
:
public class ScaleDownShowBehavior extends FloatingActionButton.Behavior
public ScaleDownShowBehavior(Context context, AttributeSet attrs)
super();
这里我们的滑动方向还是不变,监听竖着方向的滑动:
@Override
public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, ...)
return nestedScrollAxes == ViewCompat.SCROLL_AXIS_VERTICAL;
那么我们就要稍微改一下我们的开始滑动时回调这个方法了:onNestedScroll()
:
@Override
// 隐藏动画是否正在执行
private boolean isAnimatingOut = false;
public void onNestedScroll(CoordinatorLayout coordinatorLayout, FloatingActionButton child,
View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed)
if ((dyConsumed > 0 || dyUnconsumed > 0) && !isAnimatingOut
&& child.getVisibility() == View.VISIBLE) // 手指上滑,隐藏FAB
AnimatorUtil.scaleHide(child, listener);
else if ((dyConsumed < 0 || dyUnconsumed < 0) && child.getVisibility() != View.VISIBLE)
AnimatorUtil.scaleShow(child, null);// 手指下滑,显示FAB
private ViewPropertyAnimatorListener listener = new ViewPropertyAnimatorListener()
@Override
public void onAnimationStart(View view)
isAnimatingOut = true;
@Override
public void onAnimationEnd(View view)
isAnimatingOut = false;
view.setVisibility(View.GONE);
@Override
public void onAnimationCancel(View arg0)
isAnimatingOut = false;
;
好吧,代码非常少,完成了。那么我们就在string.xml中定义好,刚才我们引用的变量@string/scale_down_show_behavior
:
<string name="scale_down_show_behavior">com.yanzhenjie.definebehavior.behavior.ScaleDownShowBehavior</string>
啊呀,好激动呀,我赶紧运行一下。但是但是。。。运行后发现见鬼啊,只有FAB会跟着显示和隐藏,完全看不到Tab导航呀
,严振杰你是在忽悠人麽?哈哈哈哈,且听我细细道来。
通过监听ScaleDownShowBehavior中的view显示/隐藏来控制Tab导航栏
其实只要看过Material Design系列,Behavior之BottomSheetBehavior与BottomSheetDialog这篇文章的同学会发现,用BottomSheetBehavior
的控件默认都是隐藏起来的,需要我们去调用它的方法来控制它的View
的显示。所以我们这里需要在CoordinatorLayout
中的ContentView
滚动的时候来调用BottomSheetBehavior
的方法使它依附的View
显示与隐藏。
那么我们发现ScaleDownShowBehavior
被系统自动调用了,也触发了View
的隐藏和显示,CoordinatorLayout
这货没有给我们自动调用BottomSheetBehavior
,我们怎么办?如果你没有忘记的话,我们自定义ScaleDownShowBehavior
的时候,在onNestedScroll()
方法中有个地方是去调用了FAB的显示和隐藏,所以我们在这里加一个回调监听,让外部可以监听到它的动作,是不是同时可以控制BottomSheetBehavior
了?如果还没有向明的话看代码。
先在ScaleDownShowBehavior
中定一个Listener
:
// 外部监听显示和隐藏。
public interface OnStateChangedListener
void onChanged(boolean isShow);
然后在ScaleDownShowBehavior
的onNestedScroll()
方法中回调:
private OnStateChangedListener mOnStateChangedListener;
public void setOnStateChangedListener(OnStateChangedListener mOnStateChangedListener)
this.mOnStateChangedListener = mOnStateChangedListener;
@Override
public void onNestedScroll(CoordinatorLayout coordinatorLayout, FloatingActionButton child,
View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed)
if ((dyConsumed > 0 || dyUnconsumed > 0) && !isAnimatingOut
&& child.getVisibility() == View.VISIBLE) //往下滑
AnimatorUtil.scaleHide(child, viewPropertyAnimatorListener);
if (mOnStateChangedListener != null)
mOnStateChangedListener.onChanged(false);
else if ((dyConsumed < 0 || dyUnconsumed < 0) && child.getVisibility() != View.VISIBLE)
AnimatorUtil.scaleShow(child, null);
if (mOnStateChangedListener != null)
mOnStateChangedListener.onChanged(true);
好完美啊。来来来,设置一个监听。。。我勒个去,突然发现怎么从FAB拿到这个Behavior
啊?且看我下面的分析,保证让你柳暗花明又一村啊。
拿到FAB的Behavior对象,通过监听控制BottomSheetBehavior的View的显示/隐藏
我们这知道,给一个View
设置Behavior
对象的时候是在xml中设置,所以Behavior
是一个View
的LayoutParams
属性吧?哈哈哈明白了吧,然后Behavior
又必须和CoordinatorLayout
结合使用,不然也是扯淡,so,这个View
也必须是CoordinatorLayout
的子View,所以在ScaleDownShowBehavior
中产生了如下的一个静态方法(为了方便阅读,提示写了中文):
public static <V extends View> ScaleDownShowBehavior from(V view)
ViewGroup.LayoutParams params = view.getLayoutParams();
if (!(params instanceof CoordinatorLayout.LayoutParams))
throw new IllegalArgumentException("这个View不是CoordinatorLayout的子View");
CoordinatorLayout.Behavior behavior = ((CoordinatorLayout.LayoutParams) params).getBehavior();
if (!(behavior instanceof ScaleDownShowBehavior))
throw new IllegalArgumentException("这个View的Behaviro不是ScaleDownShowBehavior");
return (ScaleDownShowBehavior) behavior;
所以我们在Activity
中:
private BottomSheetBehavior mBottomSheetBehavior;
@Override
protected void onCreate(Bundle savedInstanceState)
super.onCreate(savedInstanceState);
setContentView(R.layout.zhihu_main);
ScaleDownShowBehavior scaleDownShowFab = ScaleDownShowBehavior.from(FAB);
scaleDownShowFab.setOnStateChangedListener(onStateChangedListener);
mBottomSheetBehavior = BottomSheetBehavior.from(findViewById(R.id.tab_layout));
private OnStateChangedListener onStateChangedListener = new OnStateChangedListener()
@Override
public void onChanged(boolean isShow)
mBottomSheetBehavior.setState(
isShow ? BottomSheetBehavior.STATE_EXPANDED
: BottomSheetBehavior.STATE_COLLAPSED);
;
哎哟喂,不知不觉中已经把我们的效果实现了,这里最重要的就是onStateChangedListener
了,这里实现了Tab导航
的隐藏和显示,它的状态是从ScaleDownShowBehavior
中回调出来的。
页面初始化好后显示Tab导航
我们上文中说道,添加了BottomSheetBehavior
属性的View,默认是隐藏的,所以我们在页面初始化时要把我们的Tab导航
显示出来:
private boolean initialize = false;
@Override
public void onWindowFocusChanged(boolean hasFocus)
super.onWindowFocusChanged(hasFocus);
if (!initialize)
initialize = true;
mBottomSheetBehavior.setState(BottomSheetBehavior.STATE_EXPANDED);
Ok,今天的博客就撸完了,本例源码传送门。
版权声明:转载必须注明本文转自严振杰的博客: http://blog.csdn.net/yanzhenjie1003
以上是关于Material Design系列,自定义Behavior实现Android知乎首页的主要内容,如果未能解决你的问题,请参考以下文章
Material Design系列,自定义Behavior之上滑显示返回顶部按钮
React Material Design:在类组件中使用带有 redux 的 React Material Design 自定义样式
android material design 风格组件(MaterialButton,MaterialButtonToggleGroup,Chip,ChipGroup)大汇总.
具有 Material Design 风格的自定义 Android 键盘
使用Material Design 创建App翻译系列----材料主题的使用(Using Material Theme)