上一节主要是讲解MaterialDesign的一些简单案例,接下来要讲的是一个比较复杂的案例:CoordinatorLayout.实际上,它就是一个类似于5大布局的viewgroup.下面我们要实现的案例如下:
1.要展示上面的界面 需要在app的build.gradle中添加依赖脚本:
compile ‘com.android.support:design:25.3.1‘
2.找到对应的布局,添加代码:
<?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" >
<!-- 创建顶部图片控件 AppBarLayout其父类是LinearLayout -->
<android.support.design.widget.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<!-- 这是内部的一个可以帮助我们上拉折叠的界面 -->
<android.support.design.widget.CollapsingToolbarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageView
android:layout_width="match_parent"
android:layout_height="200dp"
android:scaleType="fitXY"
android:src="@drawable/lf" />
</android.support.design.widget.CollapsingToolbarLayout>
</android.support.design.widget.AppBarLayout>
<!-- 创建底部可以滑动的文本界面 -->
<android.support.v4.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="@string/content"/>
</android.support.v4.widget.NestedScrollView>
</android.support.design.widget.CoordinatorLayout>
如果细心一点你会发现我们的界面是没有ActionBar的 于是在styles.xml中修改如下:
<resources>
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
<!-- Customize your theme here. -->
</style>
</resources>
于是界面展示如下,:
有没发现 文本被前面的图片控件隐藏掉了,显示不全,我们希望文字就显示在图片控件下。点击外部布局 可以看到CoordinatorLayout实际上是ViewGroup的子类。此时只需要在标签中添加一个属性layout_behavior即可达到想要的效果:
<android.support.design.widget.CoordinatorLayout
xmlns:app="http://schemas.android.com/apk/res-auto" >
<android.support.v4.widget.NestedScrollView
app:layout_behavior="@string/appbar_scrolling_view_behavior" >
</android.support.v4.widget.NestedScrollView>
</android.support.design.widget.CoordinatorLayout>
现在效果差不多了 然后滑动的时候发现只有底部的文本控件才能滑动,为了让顶部的界面也可以跟着滑动,需要可折叠的控件CollapsingToolbarLayout添加一个属性:
app:layout_scrollFlags="scroll"
layout_scrollFlags的几个属性
- 除了scroll可以实现滑动之外,还有一个属性值enterAlways,添加了该属性后,如果你上面的图片被隐藏了,并且字体的上部分也被隐藏了,此刻下拉,不会按着顺序展示出来,而是每次都先展示完图片,再显示被隐藏的字体。
app:layout_scrollFlags=”scroll|enterAlways”
2.还有一个属性,叫enterAlwaysCollapsed,该属性需要配合enterAlways一起属性,并且需要指定minHeight.代码如下:
<android.support.design.widget.CollapsingToolbarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="100dp"
app:layout_scrollFlags="scroll|enterAlways|enterAlwaysCollapsed" />
上面的代码运行效果是如下,下拉显示的时候 先显示特定的高度的图片,在慢慢显示文字,当文字显示到顶部的时候如下还往下拉,则会完全显示图片:
3.这里还有另外一个属性值exitUntilCollapsed。他可以帮助我们在上拉的过程中隐藏图片控件的只一部分。如下的代码,我们在上拉过程中图片会被部分隐藏,直到剩下高度的100dp位置可以停止隐藏,接着隐藏文字。
<android.support.design.widget.CollapsingToolbarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="100dp"
app:layout_scrollFlags="scroll|exitUntilCollapsed" >
当然,为了达到我们上面案例的样子,需要去掉android:minHeight属性。
视差
在上拉的过程中,你会发现滑动的距离远大于图片被隐藏的距离。也就是图片是按着我们往上滑的距离进行比例隐藏的,这就是视差,如何设置才属性了,找到对应的图片添加属性即可 代码如下:
<ImageView
android:layout_width="match_parent"
android:layout_height="200dp"
android:scaleType="fitXY"
android:src="@drawable/lf"
app:layout_collapseMode="parallax"
app:layout_collapseParallaxMultiplier="0.8" />
layout_collapseMode设置了parallax属性值代表以视差的方式滑动 下面的layout_collapseParallaxMultiplier属性指定了视差值的比例为0.8。
添加工具栏
为了让界面在上拉的过程中展示工具栏 这里需要给它添加一个ToolBar,因为CollapsingToolbarLayout是FrameLayout的子类,所以只需要在ImageView后面添加一个ToolBar则会覆盖在图片上面显示。?android:attr/actionBarSize高度指定的是工具栏的高度为系统ActionBar高度。
<android.support.design.widget.CollapsingToolbarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_scrollFlags="scroll|exitUntilCollapsed" >
<ImageView
android:layout_width="match_parent"
android:layout_height="200dp"
android:scaleType="fitXY"
android:src="@drawable/lf"
app:layout_collapseMode="parallax"
app:layout_collapseParallaxMultiplier="0.8" />
<android.support.v7.widget.Toolbar
android:layout_width="match_parent"
android:layout_height="?android:attr/actionBarSize"
android:background="@color/colorPrimary" />
</android.support.design.widget.CollapsingToolbarLayout>
上面的代码运行后,会发现工具栏跟着滑动一起被隐藏了实际上我们希望他置顶,那么应该添加滑动置顶的属性。那么你会想到的就是layout_collapseMode。pin值就是让某个控件可以在滑动的时候置顶的。并且一开始ToolBar是没有显示出来的,所以应该将颜色去掉。代码如下:
<android.support.v7.widget.Toolbar
android:layout_width="match_parent"
android:layout_height="?android:attr/actionBarSize"
app:layout_collapseMode="pin" />
为了让图片在向上拉的过程中,逐渐渐变成某单一色块,那么我们需要为CollapsingToolbarLayout添加一个新的属性contentScrim并指定一个颜色值:
<android.support.design.widget.CollapsingToolbarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_scrollFlags="scroll|exitUntilCollapsed"
app:contentScrim="@color/colorPrimary">
添加字体
图片中间本来是有字体的,那么他是如何实现的呢?
<android.support.design.widget.CollapsingToolbarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_scrollFlags="scroll|exitUntilCollapsed"
app:contentScrim="@color/colorPrimary"
app:title="I like Lufu !"
app:expandedTitleMarginStart="200dp"
app:expandedTitleMarginEnd="0dp" >
- title指定标题文本
- expandedTitleMarginStart指定文本还没上滑时距离左边的间距
- expandedTitleMarginEnd指定滑动后距离左边的间距
字体是黑色的是不是很难看,可以指定字体样式 首先在style.xml文件中定义样式:
<style name="TextStyle" parent="TextAppearance.Design.CollapsingToolbar.Expanded">
<item name="android:textColor">@android:color/white</item>
<item name="android:textSize">18sp</item>
</style>
在CollapsingToolbarLayout控件中指定属性:
app:expandedTitleTextAppearance="@style/TextStyle"
添加FloatingActionButton
从图中可以看出该控件是在两个大的容器中间,也是在CoordinatorLayout的内部。实际上他有一个锚点的概念,就是根据界面存在的某个控件来布局的。代码如下:
<android.support.design.widget.CoordinatorLayout >
<!-- 创建顶部图片控件 AppBarLayout其父类是LinearLayout -->
<android.support.design.widget.AppBarLayout
...
android:id="@+id/appbar">
</android.support.design.widget.AppBarLayout>
<android.support.design.widget.FloatingActionButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:fabSize="mini"
android:src="@mipmap/ic_launcher"
app:backgroundTint="@color/colorPrimary"
app:layout_anchor="@id/appbar"
app:layout_anchorGravity="bottom|center_horizontal" />
</android.support.design.widget.CoordinatorLayout>
- layout_anchor指定以某一个控件来布局
- layout_anchorGravity指定在该控件的某个位置。
自定义布局依赖
接下来我们要重新回去理解scrollView的layout_behavior属性。该属性接收一个字符串,帮我们滑动scrollView的时候,顶部的appbarlayout也跟着移动。接下来我们学习下如何自定义一个layout_behavior。
实际上,这里我们要做的就是让图片移动,只要图片一移动 文本也跟着一起移动。这个操作如何实现呢,首先看下布局,这里我们还是使用CoordinatorLayout布局(翻译过来叫协调者布局 通过他来协调内部的操作)。
<?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">
<ImageView
android:id="@+id/iv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@mipmap/ic_launcher" />
<TextView
android:id="@+id/tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_behavior="com.example.demo.MyBehavior"
android:text="aaaaaaaaaa" />
</android.support.design.widget.CoordinatorLayout>
注意到了上面的图片控件,随着手指的移动而移动,我们的代码很简单,这里不做过多解释,至于TextView的layout_behavior属性如何配置,后面会讲到:
public class BehaviorActivity extends AppCompatActivity implements View.OnTouchListener {
private ImageView iv;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_behavior);
iv = (ImageView) findViewById(R.id.iv);
iv.setOnTouchListener(this);
}
@Override
public boolean onTouch(View v, MotionEvent event) {
if (event.getAction()==MotionEvent.ACTION_MOVE){
iv.setX(event.getRawX());
iv.setY(event.getRawY());
}
return true;
}
}
现在的重点来了,怎么让文本控件跟图片控件一起移动呢,经过分析:
文本控件:时刻观察图片控件的行为(观察者)
图片控件:被文本控件监听的对象(被观察者)
接下来我们需要创建一个类,用来绑定这2个对象。
/**
* Behavior<TextView> 内部的泛型就是观察者的类型
*/
public class MyBehavior extends CoordinatorLayout.Behavior<TextView> {
/**
* 默认需要实现如下两个构造器
* */
public MyBehavior() {
}
public MyBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
}
/**
* 告诉系统我们要监听的对象是ImageView的图片控件
* 实际上一个容器可能有多个图片控件 我更愿意使用tag属性来区别某个具体的控件
* @param dependency 被观察者对象
* @param child 观察者对象
* */
@Override
public boolean layoutDependsOn(CoordinatorLayout parent, TextView child, View dependency) {
return dependency instanceof ImageView;
}
/**
* 拿到被观察者对象的x y坐标,并计算出观察者的x y坐标
*
* @param dependency 被观察者对象
* @param child 观察者对象
* */
@Override
public boolean onDependentViewChanged(CoordinatorLayout parent, TextView child, View dependency) {
float x = dependency.getX();
float y = dependency.getY();
child.setX(x);
child.setY(y+10+dependency.getHeight());
return true;
}
}
写完该类后,需要将该类配置到布局文件中,实际上也就是将类的全路径的字符串添加到观察者的标签中,这里指的就是TextView.
<TextView
android:id="@+id/tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_behavior="com.example.demo.MyBehavior"
android:text="aaaaaaaaaa" />
说到这里已经把该Demo写完了。接下来再来看看第一个Demo中为何添加一个字符串就能让头部的AppBarLayout跟着一起移动的。
app:layout_behavior="@string/appbar_scrolling_view_behavior"
<string name="appbar_scrolling_view_behavior" translatable="false">android.support.design.widget.AppBarLayout$ScrollingViewBehavior</string>
实际上该字符串指定的就是ScrollingViewBehavior类。让我们看看该类2个重要的方法吧,从下面可以看出,被观察者实际上就是AppBarLayout,也就是系统内部会根据AppBarLayout做出对应的调整。
@Override
public boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency) {
// We depend on any AppBarLayouts
return dependency instanceof AppBarLayout;
}
@Override
public boolean onDependentViewChanged(CoordinatorLayout parent, View child,
View dependency) {
offsetChildAsNeeded(parent, child, dependency);
return false;
}