欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

Material Design系列之Behavior实现Android知乎首页

程序员文章站 2024-03-05 18:27:43
本博客目的:仿知乎首页向上滑动时动画隐藏toolbar、flocationactionbutton、tab导航,下滑时显示,如果和你的期望不同,那么你可以不需要看了,免的浪...

本博客目的:仿知乎首页向上滑动时动画隐藏toolbar、flocationactionbutton、tab导航,下滑时显示,如果和你的期望不同,那么你可以不需要看了,免的浪费你的宝贵时间噢。

效果预览

知乎效果:

Material Design系列之Behavior实现Android知乎首页

本博客实现效果:

Material Design系列之Behavior实现Android知乎首页

今天效果的源代码下载链接在文章末尾。

实现分析

这个效果其实并不难实现,但是它的用处很大,当用户手指上滑,屏幕上显示下方内容的时候,隐藏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);
 }
}

源码下载:http://xiazai.jb51.net/201609/yuanma/androidbehavior(jb51.net).rar

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。