简单实现一个多元素互动的AppBarLayout头部滑动效果
2018-05-08 本文已影响137人
留给时光吧
效果如下:
在没有AppBarLayout之前,这种效果实现起来应该是挺复杂的,但是引入AppBarLayout后就很随意了,先看布局文件:
<android.support.design.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto">
<android.support.design.widget.AppBarLayout
android:layout_width="match_parent"
android:layout_height="250dp"
android:background="@color/colorTransparent"
android:id="@+id/app_bar"
app:elevation="0dp"
>
<include layout="@layout/layout_weather_head"/>
</android.support.design.widget.AppBarLayout>
<android.support.v4.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/ns"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:descendantFocusability="blocksDescendants"
>
<com.app.chenyang.sweather.ui.widget.WeatherChartView
android:id="@+id/daily_chart"
android:layout_width="match_parent"
android:layout_height="230dp"
android:layout_marginLeft="15dp"
android:layout_marginRight="15dp"/>
<com.app.chenyang.sweather.ui.widget.MyGridView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/gv_living_details"
android:numColumns="2"
>
</com.app.chenyang.sweather.ui.widget.MyGridView>
</LinearLayout>
</android.support.v4.widget.NestedScrollView>
</android.support.design.widget.CoordinatorLayout>
我们那些随着滑动互动的元素就主要就是放在AppBarLayout中。这个布局有几个关键点。
- 1 AppBarLayout的app:elevation属性要为0dp,否则AppBarLayout下面会有一条横线,影响效果。其次背景要为透明
- 2 NestedScrollView的app:layout_behavior为@string/appbar_scrolling_view_behavior
- AppBarLayout内部的布局layout_weather_head根布局要为RelativeLayout或者FrameLayout,若为Linerlayout则单个元素的移动会受影响。然后app:layout_scrollFlags属性要为"scroll|exitUntilCollapsed|snap"。最后要有最小高度,否则会使整个AppBarLayout划出去,如android:minHeight="100dp"。这个布局比较长就不贴出来了,布局内容可以根据需求自定
接下来看java代码。我们的效果是随着滑动来改变各个元素的位置,所以就要监听AppBarLayout的滑动事件,由于一些控件可能会未加载完成,导致测量出一些数据为0,我们首先添加一个布局监听,可以任意找一个AppBarLayout内的控件添加:
tvCity.getViewTreeObserver().addOnGlobalLayoutListener(this);
在布局建立后我们就能拿到每个控件的真实数据,然后根据滑动的距离来动态的改变每个控件位置及其他属性,逻辑不复杂,只不过要有比较缜密的计算,具体实现如下,主要部分有注释:
@Override
public void onGlobalLayout() {
tvCity.getViewTreeObserver().removeOnGlobalLayoutListener(this);
nestedScrollView.fullScroll(View.FOCUS_UP);
int ivArrowDistance = ivPageBack.getRight();
//获取AppBarLayout最大滑动距离
final int scrollRange = appBar.getTotalScrollRange();
//预先计算各个部分要移动的距离
final double tvCityLeftTranslationDistance = tvCity.getLeft() + (tvCity.getWidth() * ZOOM_RATIO * 0.5) - ivArrowDistance;
final double tvTimeLeftTranslationDistance = tvTime.getLeft() + (tvTime.getWidth() * ZOOM_RATIO * 0.5) - ivArrowDistance;
final double llWeatherRightTranslationDistance = (BaseUtils.SCREEN_WIDTH - llWeather.getRight()) + (llWeather.getWidth() * ZOOM_RATIO * 0.5) - ivArrowDistance;
final double llWeatherTopTranslationDistance = ivPageForwardTopMargin + scrollRange/2 + ivPageBack.getHeight() * 0.5 - llWeather.getHeight() * 0.5 - llWeatherTopMargin;
appBar.addOnOffsetChangedListener(new AppBarLayout.OnOffsetChangedListener() {
@Override
public void onOffsetChanged(AppBarLayout appBarLayout, int verticalOffset) {
//获得滑动具体,计算比例
int scrollDistance = Math.abs(verticalOffset);
float scrollPercentage = (float) scrollDistance/scrollRange;
//两端箭头处理,主要是随着滑动上下移动
ivPageBackParams.topMargin = ivPageBackTopMargin + scrollDistance/2;
ivPageForwardParams.topMargin = ivPageForwardTopMargin + scrollDistance/2;
ivPageBack.setLayoutParams(ivPageBackParams);
ivPageForward.setLayoutParams(ivPageForwardParams);
//底部AQI和天气文字的处理,只要是设置透明度
if(scrollDistance > ALPHA_DISTANCE){
tvAQI.setAlpha(0);
tvWeather.setAlpha(0);
}else{
tvAQI.setAlpha(1-scrollDistance/(float)ALPHA_DISTANCE);
tvWeather.setAlpha(1-scrollDistance/(float)ALPHA_DISTANCE);
}
//第一行城市名称及时间的处理
//向上滑动时文字向右移动且适当缩小
tvCityParams.topMargin = tvCityTopMargin + scrollDistance;
tvCityParams.leftMargin = -(int) (tvCityLeftTranslationDistance * scrollPercentage);
tvTimeParams.topMargin = tvTimeTopMargin + scrollDistance;
tvTimeParams.leftMargin = -(int) (tvTimeLeftTranslationDistance * scrollPercentage);
tvCity.setLayoutParams(tvCityParams);
tvCity.setScaleX(1 - ZOOM_RATIO * scrollPercentage);
tvCity.setScaleY(1 - ZOOM_RATIO * scrollPercentage);
tvTime.setLayoutParams(tvTimeParams);
tvTime.setScaleX(1 -ZOOM_RATIO * scrollPercentage);
tvTime.setScaleY(1 -ZOOM_RATIO * scrollPercentage);
//这里将天气图标和温度作为一整块处理
//向上滑动时整体右移且缩小
llWeatherParams.topMargin = (int) (llWeatherTopTranslationDistance * scrollPercentage) + llWeatherTopMargin;
llWeatherParams.rightMargin = -(int) (llWeatherRightTranslationDistance * scrollPercentage);
llWeather.setScaleX(1 - ZOOM_RATIO * scrollPercentage);
llWeather.setScaleY(1 - ZOOM_RATIO * scrollPercentage);
}
});
}
起始这种方法是比较直接原始的,也很简单,就是实现起来比较繁琐,以后遇到更简便的方法在更新ヾ(o・ω・)ノ