热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

钉钉个人头像动画的实现

本文的最终效果如下:头像动画.gif头像动画的实现主要是利用了appbarLayout的AppBarLayout.OnOffsetChangedListener接口实现的,头像和姓

本文的最终效果如下:

《钉钉个人头像动画的实现》 头像动画.gif

头像动画的实现主要是利用了appbarLayout的AppBarLayout.OnOffsetChangedListener接口实现的,头像和姓名及sexView是两个线性的布局。通过监听appbar的移动,算出appbar距离的变化率。用appbar距离的变化率作为两个线性布局的padding的变化率和头像大小缩放的变化率。

1.先看布局文件吧


xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_
android:layout_>
android:id="@+id/appBarLayout"
android:layout_
android:layout_>
android:id="@+id/collapsing_toolbar"
android:layout_
android:layout_
app:layout_scrollFlags="scroll|exitUntilCollapsed|snap">
android:id="@+id/toolbar"
android:layout_
android:layout_
app:theme="@style/ThemeOverlay.AppCompat.Dark"
app:layout_collapseMode="pin"/>
android:id="@+id/stuff_container"
android:layout_
android:layout_
app:collapsedPadding="52dp">
android:id="@+id/profile"
android:layout_
android:layout_
android:fitsSystemWindows="true"
android:layout_gravity="center"/>

android:id="@+id/stuff_container1"
android:layout_
android:layout_
app:collapsedPadding="100dp">
android:id="@+id/name"
android:layout_
android:layout_
android:layout_gravity="center_vertical"
android:gravity="center"
android:text="houlucky"
android:textColor="#FFFFFF"
android:textSize="16sp"
/>
android:id="@+id/sex_view"
android:layout_
android:layout_
android:layout_gravity="center_vertical"
android:src="@mipmap/boy"/>



android:id="@+id/linear_layout"
android:layout_
android:layout_
android:layout_marginTop="170dp"
android:layout_gravity="top">
android:id="@+id/follows_key"
android:layout_
android:layout_
android:layout_marginRight="5dp"
android:text="@string/follows"
android:textColor="#FFFFFF"
android:textSize="12sp"
/>
android:id="@+id/follows_value"
android:layout_
android:layout_
android:textColor="#FFFFFF"
android:textSize="12sp"
/>
android:layout_
android:layout_
android:layout_marginLeft="14dp"
android:layout_marginRight="14dp"
android:layout_marginTop="3dp"
android:background="#FFFFFF"/>
android:id="@+id/fans_key"
android:layout_
android:layout_
android:layout_marginRight="5dp"
android:text="@string/fans"
android:textColor="#FFFFFF"
android:textSize="12sp"
/>
android:id="@+id/fans_value"
android:layout_
android:layout_
android:textColor="#FFFFFF"
android:textSize="12sp"
/>

android:id="@+id/role"
android:layout_
android:layout_
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"
/>
android:orientation="vertical"
android:layout_
android:layout_
app:layout_behavior="@string/appbar_scrolling_view_behavior">
android:id="@+id/tab"
android:layout_
android:layout_
android:background="#FFFFFF"
android:theme="@style/AppTheme"
app:tabIndicatorColor="@color/userIndexTabIndicatorColor"
app:tabSelectedTextColor="@color/userIndexTabSelectedTextColor"
app:tabTextColor="@color/userIndexTabTextColor"/>
android:id="@+id/view_pager"
android:layout_
android:layout_
android:layout_marginTop="10dp"
android:background="#FFFFFF"
/>


attrs.xml










上面布局文件中的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 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的大小

《钉钉个人头像动画的实现》 初始.png
《钉钉个人头像动画的实现》 移动中.png

图中的黑色方块就是我们自定义的SlideView,随着SlideView的滑动SlideView里的图片也随着移动,并且图片慢慢变小通过设置图片的layout_gravity使图片始终处于SlideView的居中位置,又设置SlideVIew的padding使图片有了一个向左移动的效果,下面的名字和性别的图片位于同一个SlideView,移动的方式同上面的头像。但通过设置他们高度的不同使下面的名字显示位置和上面的头像不同。

注意:ViewPager的根布局要设置为NestedScrollView,否则不能实现联动的效果。

Demo地址:https://github.com/houlucky/SlideViewDemo
欢迎Star!!!!


推荐阅读
  • YOLOv7基于自己的数据集从零构建模型完整训练、推理计算超详细教程
    本文介绍了关于人工智能、神经网络和深度学习的知识点,并提供了YOLOv7基于自己的数据集从零构建模型完整训练、推理计算的详细教程。文章还提到了郑州最低生活保障的话题。对于从事目标检测任务的人来说,YOLO是一个熟悉的模型。文章还提到了yolov4和yolov6的相关内容,以及选择模型的优化思路。 ... [详细]
  • 后台获取视图对应的字符串
    1.帮助类后台获取视图对应的字符串publicclassViewHelper{将View输出为字符串(注:不会执行对应的ac ... [详细]
  • XML介绍与使用的概述及标签规则
    本文介绍了XML的基本概念和用途,包括XML的可扩展性和标签的自定义特性。同时还详细解释了XML标签的规则,包括标签的尖括号和合法标识符的组成,标签必须成对出现的原则以及特殊标签的使用方法。通过本文的阅读,读者可以对XML的基本知识有一个全面的了解。 ... [详细]
  • 本文介绍了Android 7的学习笔记总结,包括最新的移动架构视频、大厂安卓面试真题和项目实战源码讲义。同时还分享了开源的完整内容,并提醒读者在使用FileProvider适配时要注意不同模块的AndroidManfiest.xml中配置的xml文件名必须不同,否则会出现问题。 ... [详细]
  • Android开发实现的计时器功能示例
    本文分享了Android开发实现的计时器功能示例,包括效果图、布局和按钮的使用。通过使用Chronometer控件,可以实现计时器功能。该示例适用于Android平台,供开发者参考。 ... [详细]
  • 在Xamarin XAML语言中如何在页面级别构建ControlTemplate控件模板
    本文介绍了在Xamarin XAML语言中如何在页面级别构建ControlTemplate控件模板的方法和步骤,包括将ResourceDictionary添加到页面中以及在ResourceDictionary中实现模板的构建。通过本文的阅读,读者可以了解到在Xamarin XAML语言中构建控件模板的具体操作步骤和语法形式。 ... [详细]
  • 移动端常用单位——rem的使用方法和注意事项
    本文介绍了移动端常用的单位rem的使用方法和注意事项,包括px、%、em、vw、vh等其他常用单位的比较。同时还介绍了如何通过JS获取视口宽度并动态调整rem的值,以适应不同设备的屏幕大小。此外,还提到了rem目前在移动端的主流地位。 ... [详细]
  • 如何在HTML中获取鼠标的当前位置
    本文介绍了在HTML中获取鼠标当前位置的三种方法,分别是相对于屏幕的位置、相对于窗口的位置以及考虑了页面滚动因素的位置。通过这些方法可以准确获取鼠标的坐标信息。 ... [详细]
  • Activiti7流程定义开发笔记
    本文介绍了Activiti7流程定义的开发笔记,包括流程定义的概念、使用activiti-explorer和activiti-eclipse-designer进行建模的方式,以及生成流程图的方法。还介绍了流程定义部署的概念和步骤,包括将bpmn和png文件添加部署到activiti数据库中的方法,以及使用ZIP包进行部署的方式。同时还提到了activiti.cfg.xml文件的作用。 ... [详细]
  • C#多线程解决界面卡死问题的完美解决方案
    当界面需要在程序运行中不断更新数据时,使用多线程可以解决界面卡死的问题。一个主线程创建界面,使用一个子线程执行程序并更新主界面,可以避免卡死现象。本文分享了一个例子,供大家参考。 ... [详细]
  • 微信小程序导航跟随的实现方法
    本文介绍了在微信小程序中实现导航跟随的方法。通过设置导航的position属性和绑定滚动事件,可以实现页面向下滚动到导航位置时,导航固定在页面最上方;页面向上滚动到导航位置时,导航恢复到原始位置;点击导航可以平滑跳转到相应位置。代码示例也给出了具体实现方法。 ... [详细]
  • 在一对一直播源码使用过程中,有时会出现软键盘切换闪屏问题,就是当切换表情的时候屏幕会跳动,因此要对一对一直播源码表情面板无缝切换进行优化。 ... [详细]
  • fileuploadJS@sectionscripts{<scriptsrc~Contentjsfileuploadvendorjquery.ui.widget.js ... [详细]
  • 人脸检测 pyqt+opencv+dlib
    一、实验目标绘制PyQT界面,调用摄像头显示人脸信息。在界面中,用户通过点击不同的按键可以实现多种功能:打开和关闭摄像头, ... [详细]
  • 注:根据Qt小神童的视频教程改编概论:利用最新的Qt5.1.1在windows下开发的一个小的时钟程序,有指针与表盘。1.Qtforwindows开发环境最新的Qt已经集 ... [详细]
author-avatar
低契一巴掌
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有