本文的最终效果如下:
头像动画的实现主要是利用了appbarLayout的AppBarLayout.OnOffsetChangedListener接口实现的,头像和姓名及sexView是两个线性的布局。通过监听appbar的移动,算出appbar距离的变化率。用appbar距离的变化率作为两个线性布局的padding的变化率和头像大小缩放的变化率。
1.先看布局文件吧
<?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/appBarLayout"
android:layout_width="match_parent"
android:layout_height="225dp">
<android.support.design.widget.CollapsingToolbarLayout
android:id="@+id/collapsing_toolbar"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_scrollFlags="scroll|exitUntilCollapsed|snap">
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:theme="@style/ThemeOverlay.AppCompat.Dark"
app:layout_collapseMode="pin"/>
<com.campussay.modules.user.index.view.SlideView
android:id="@+id/stuff_container"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:collapsedPadding="52dp">
<ImageView
android:id="@+id/profile"
android:layout_width="@dimen/default_expanded_image_size"
android:layout_height="@dimen/default_expanded_image_size"
android:fitsSystemWindows="true"
android:layout_gravity="center"/>
</com.campussay.modules.user.index.view.SlideView>
<com.campussay.modules.user.index.view.SlideView
android:id="@+id/stuff_container1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:collapsedPadding="100dp">
<TextView
android:id="@+id/name"
android:layout_width="70dp"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:gravity="center"
android:text="houlucky"
android:textColor="#FFFFFF"
android:textSize="16sp"
style="@style/text_shadow"/>
<ImageView
android:id="@+id/sex_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:src="@mipmap/boy"/>
</com.campussay.modules.user.index.view.SlideView>
</android.support.design.widget.CollapsingToolbarLayout>
</android.support.design.widget.AppBarLayout>
<LinearLayout
android:id="@+id/linear_layout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="170dp"
android:layout_gravity="top">
<TextView
android:id="@+id/follows_key"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginRight="5dp"
android:text="@string/follows"
android:textColor="#FFFFFF"
android:textSize="12sp"
style="@style/text_shadow"/>
<TextView
android:id="@+id/follows_value"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="#FFFFFF"
android:textSize="12sp"
style="@style/text_shadow"/>
<View
android:layout_width="0.5dp"
android:layout_height="10dp"
android:layout_marginLeft="14dp"
android:layout_marginRight="14dp"
android:layout_marginTop="3dp"
android:background="#FFFFFF"/>
<TextView
android:id="@+id/fans_key"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginRight="5dp"
android:text="@string/fans"
android:textColor="#FFFFFF"
android:textSize="12sp"
style="@style/text_shadow"/>
<TextView
android:id="@+id/fans_value"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="#FFFFFF"
android:textSize="12sp"
style="@style/text_shadow"/>
</LinearLayout>
<TextView
android:id="@+id/role"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/linear_layout"
android:layout_gravity="top"
android:layout_marginBottom="13dp"
android:layout_marginTop="190dp"
android:background="@drawable/text_view_bg"
android:paddingBottom="3dp"
android:paddingLeft="10dp"
android:paddingRight="10dp"
android:paddingTop="3dp"
android:textColor="#FFFFFF"
android:textSize="11sp"
style="@style/text_shadow"/>
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<android.support.design.widget.TabLayout
android:id="@+id/tab"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#FFFFFF"
android:theme="@style/AppTheme"
app:tabIndicatorColor="@color/userIndexTabIndicatorColor"
app:tabSelectedTextColor="@color/userIndexTabSelectedTextColor"
app:tabTextColor="@color/userIndexTabTextColor"/>
<android.support.v4.view.ViewPager
android:id="@+id/view_pager"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="10dp"
android:background="#FFFFFF"
/>
</LinearLayout>
</android.support.design.widget.CoordinatorLayout>
attrs.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="test">
<attr name="collapsedPadding" format="dimension" />
<attr name="expandedPadding" format="dimension" />
<attr name="collapsedImageSize" format="dimension" />
<attr name="expandedImageSize" format="dimension" />
</declare-styleable>
</resources>
上面布局文件中的SlideView就是我们的自定义的可以随appbar的滑动而滑动的view
2.SlideView.java
package com.campussay.modules.user.index.view;
/**
* Created by Houxy on 2016/7/29.
*/
import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.support.annotation.NonNull;
import android.support.design.widget.AppBarLayout;
import android.support.v7.widget.Toolbar;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewParent;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import com.campussay.R;
public class SlideView extends LinearLayout implements AppBarLayout.OnOffsetChangedListener{
private View avatarView;
private TextView titleView;
private float collapsedPadding;
private float expandedPadding;
private float expandedImageSize;
private float collapsedImageSize;
private boolean valuesCalculatedAlready = false;
private Toolbar toolbar;
private AppBarLayout appBarLayout;
private float toolBarHeight;
private float expandedHeight;
private float maxOffset;
public SlideView(Context context) {
this(context, null);
init();
}
public SlideView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.test);
try {
collapsedPadding = a.getDimension(R.styleable.test_collapsedPadding, -1);
expandedPadding = a.getDimension(R.styleable.test_expandedPadding, -1);
collapsedImageSize = a.getDimension(R.styleable.test_collapsedImageSize, -1);
expandedImageSize = a.getDimension(R.styleable.test_expandedImageSize, -1);
} finally {
a.recycle();
}
final Resources resources = getResources();
if (collapsedImageSize < 0) {
collapsedImageSize = resources.getDimension(R.dimen.default_collapsed_image_size);
}
if (expandedImageSize < 0) {
expandedImageSize = resources.getDimension(R.dimen.default_expanded_image_size);
}
if (collapsedPadding < 0) {
collapsedPadding = resources.getDimension(R.dimen.default_collapsed_padding);
}
if (expandedPadding < 0) {
expandedPadding = resources.getDimension(R.dimen.default_expanded_padding);
}
}
private void init() {
setOrientation(HORIZONTAL);
}
@NonNull
private AppBarLayout findParentAppBarLayout() {
ViewParent parent = this.getParent();
if (parent instanceof AppBarLayout) {
return ((AppBarLayout) parent);
} else if (parent.getParent() instanceof AppBarLayout) {
return ((AppBarLayout) parent.getParent());
} else {
throw new IllegalStateException("Must be inside an AppBarLayout"); //TODO actually, a collapsingtoolbar
}
}
//此方法在onDraw方法之前调用,也就是view还没有画出来的时候,可以在此方法中去执行一些初始化的操作
protected void onAttachedToWindow() {
super.onAttachedToWindow();
findViews();
if (!isInEditMode()) {
appBarLayout.addOnOffsetChangedListener(this);
} else {
setExpandedValuesForEditMode();
}
}
private void setExpandedValuesForEditMode() {
calculateValues();
updateViews(1f, 0);
}
private void findViews() {
appBarLayout = findParentAppBarLayout();
toolbar = findSiblingToolbar();
avatarView = findAvatar();
titleView = findTitle();
}
private View findAvatar() {
View avatar = null;
if(getChildAt(0) instanceof ImageView)
avatar = getChildAt(0);
return avatar;
}
private TextView findTitle() {
TextView title = null;
if(getChildAt(0) instanceof TextView){
title = (TextView) getChildAt(0);
}
return title;
}
@NonNull //使用@NonNull注解修饰的参数不能为null
private Toolbar findSiblingToolbar() {
ViewGroup parent = ((ViewGroup) this.getParent());
for (int i = 0, c = parent.getChildCount(); i < c; i++) {
View child = parent.getChildAt(i);
if (child instanceof Toolbar) {
return (Toolbar) child;
}
}
throw new IllegalStateException("No toolbar found as sibling");
}
@Override
public void onOffsetChanged(AppBarLayout appBarLayout, int offset) {
if (!valuesCalculatedAlready) {
calculateValues();
valuesCalculatedAlready = true;
}
float expandedPercentage = 1 - (-offset / maxOffset);
updateViews(expandedPercentage, offset);
}
private void calculateValues() {
toolBarHeight = toolbar.getHeight();
expandedHeight = appBarLayout.getHeight() - toolbar.getHeight();
maxOffset = expandedHeight;
//使控件处在居中的位置
//expandedPadding 布局最开始距离左侧的padding
if( avatarView != null){
expandedPadding = (appBarLayout.getWidth() - expandedImageSize) / 2;
}else if(titleView != null){
expandedPadding = (appBarLayout.getWidth() - titleView.getWidth() ) / 2;
}
}
//expandHeight appbar的最大移动距离
//toolBarHeight 固定在顶上的toolbar的高度
//expandedPercentage 1-0 inversePercentage 0-1
//collapsed 折叠时的 //expanded 展开时的,初始的
private void updateViews(float expandedPercentage, int currentOffset) {
float inversePercentage = 1 - expandedPercentage;
float translation = -currentOffset + ((float) toolbar.getHeight() * expandedPercentage);
float currHeight = 0;
if (avatarView != null) {
currHeight = toolBarHeight + (expandedHeight - toolBarHeight ) / 4 * expandedPercentage;
} else if (titleView != null) {
currHeight = toolBarHeight +( expandedHeight - toolBarHeight / 2) * expandedPercentage;
}
float currentPadding = expandedPadding + (collapsedPadding - expandedPadding) * inversePercentage;
float currentImageSize = collapsedImageSize + (expandedImageSize - collapsedImageSize) * expandedPercentage;
setContainerOffset(translation);
setContainerHeight((int) currHeight);
setPadding((int) currentPadding);
setAvatarSize((int) currentImageSize);
}
private void setContainerOffset(float translation) {
this.setTranslationY(translation);
}
private void setContainerHeight(int currHeight) {
this.getLayoutParams().height = currHeight;
}
private void setPadding(int currentPadding) {
this.setPadding(currentPadding, 0, 0, 0);
}
private void setAvatarSize(int currentImageSize) {
if (avatarView != null) {
avatarView.getLayoutParams().height = currentImageSize;
avatarView.getLayoutParams().width = currentImageSize;
}
}
}
首先,我们用SlideVIew继承自LinearLayout并实现AppBarLayout.OnOffsetChangedListener接口,在接口onOffsetChanged(AppBarLayout appBarLayout, int offset)回调函数中,通过updateViews();更新SlideView的Y方向的位移,更新SlideView的高度,更新SlideVIew的padding,如果SlideView里有ImageView则更新ImageView的大小
图中的黑色方块就是我们自定义的SlideView,随着SlideView的滑动SlideView里的图片也随着移动,并且图片慢慢变小通过设置图片的layout_gravity使图片始终处于SlideView的居中位置,又设置SlideVIew的padding使图片有了一个向左移动的效果,下面的名字和性别的图片位于同一个SlideView,移动的方式同上面的头像。但通过设置他们高度的不同使下面的名字显示位置和上面的头像不同。
注意:ViewPager的根布局要设置为NestedScrollView,否则不能实现联动的效果。
Demo地址:https://github.com/houlucky/SlideViewDemo
欢迎Star!!!!