微信自发布以来,底部导航栏的动画一直让开发者津津乐道,而且伴随着版本更新,底部导航栏的动画也一直在改进。我最近在闲暇之余,看了下微信的底部导航栏动画,于是思考了下这个动画的原理,感觉非常有意思,于是写下这篇文章。
下图就是我实现的效果,大家可以对比下微信的效果,几乎可以以假乱真。
=======================================================================
关于这个动画的过程,我刚开始了是瞅了老半天了,因为如果我们不了解动画的过程也是无从去实现了,所以动画过程很重要,这个动画其实有两个过程。
-
首先是默认图片的轮廓变色。
-
轮廓变色到一定程度后,整个图片出现了绿色的填充效果,也就是整个图片开始变绿,直到整个图片完全变为了绿色。其实这是两个图片的透明度变换的达成的效果。
/ 动画实现原理 /首先我们从整体上看,滑动的页面可以用ViewPager实现,在滑动的过程中,通过监听ViewPager的滑动事件,可以获取一个滑动的比例值。底部的导航栏的4个Tab可以用自定义一个View来实现,我把这个自定义的View叫做TabView。那么,在滑动的过程中,当前页面的TabView执行褪色动画,后一个页面执行变色动画。动画到底执行到哪一步,肯定就是由ViewPager的滑动比例值决定的。因此TabView需要一个接收动画进度比例值的方法来控制动画的程度。/ 代码实现 /俗话说得好,Talk is cheap, show me the code!。那我们就通过代码来实现我们之前的猜想吧,这肯定是一段非常激情的旅程!由于不想篇幅过大,因此我省略了ViewPager的一些样板代码,因为这些属于基本功。如果不会用ViewPager,在网上随便搜索就是一大堆的文章,很轻松就掌握了。那么本文主要就是解决如何自定义这个TabView。自定义View有很多方式,我相信很多人比我还懂。而我选择的是组合系统控件的方式来实现这个自定义View。那么可能有人问我,如果为了更好的绘制性能,能不能完全的自定义一个View来实现呢?这当然是可以的,学完本文你就可以做这个牛逼的操作。然而,这点绘制性能的提升,其实在现在的高配置的手机上是可以忽略的。那么为了开发效率,组合系统控件应该是首选。/ 实现组合控件的布局 /TabView需要的组合控件的布局如下。
// tab_layout.xml
<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android=“http://schemas.android.com/apk/res/android”
android:layout_width=“match_parent”
android:layout_height=“40dp”
android:gravity=“center_horizontal”
android:orientation=“vertical”>
<FrameLayout
android:layout_width=“wrap_content”
android:layout_height=“0dp”
android:layout_weight=“1”>
<ImageView
android:id=“@+id/tab_image”
android:layout_width=“wrap_content”
android:layout_height=“match_parent” />
<ImageView
android:id=“@+id/tab_image_top”
android:layout_width=“wrap_content”
android:layout_height=“match_parent” />
<TextView
android:id=“@+id/tab_title”
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”
android:textSize=“12sp” />
布局的TextView肯定是用来显示标题的,然而还有两个ImageView,为何这样设计呢?这与我们动画的实现有关。@+id/tab_image的ImageView在底部,它是用来显示一个默认的图片的,我称它为轮廓图,例如第一个页面的TabView的轮廓图如下。 我们需要对这个轮廓进行变色处理,大家可以观察一下动画的过程,第一个过程很显然是轮廓的变色。@+id/tab_image_top的ImageView在上面,它是用来显示一个页面被选中后的图片,也是动画最终要显示的图片,例如第一个页面的TabView的选中图片如下。 现在来说明下如何用这个布局来实现动画。
-
首先所有的TabView都显示轮廓图,选中图都进行隐藏。如何隐藏呢,我选择使用透明度来隐藏选中图,因为整个动画过程有透明度的变换。
-
当滑动ViewPager的时候,TabView获取滑动的进度值,我们就让轮廓图的轮廓开始变色。那么怎么变色呢,有一个很方便的方法,就是Drawable.setTint()方法。这个方法的原理就是PorterDuff.Mode.DST_IN混合模式。如果大家有兴趣,可以去研究下原理。
-
当ViewPager滑动到一定距离的时候,如果松开手指,页面会自动滑动到下一个页面,这个比例值到底是多少呢?我暂时还没有考究,我假定是0.5吧。当滑动的比较超过0.5的时候,就要让轮廓图的透明度逐渐变是0,也就是慢慢地的看不见了,同时,选中图的透明度逐渐变为255,也是慢慢的清晰了。如此一来,就会出现轮廓图的整体颜色填充效果。
怎么样,实现思路是不是有点意思,那么我们来根据这个思路来实现这个自定义ViewTabView吧。/ 实现TabView /加载布局既然有了布局,那么首先用在TabView的构造函数中来加载这个布局。
public TabView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
// 加载布局
inflate(context, R.layout.tab_layout, this);
}
自定义属性与解析为了更好的在XML布局中使用TabView,我为TabView抽取的自定义属性。
// res/values/tabview_attrs.xml
<?xml version="1.0" encoding="utf-8"?>-
tabColor代表变色最终显示的颜色,这个颜色可以从选中图中用取色器获取。
-
tabImage代表默认显示的轮廓图。
-
tabSelectedImage代表选中后的图。
-
tabTitle代表要显示的标题。
有了这些自定义属性,那么在TabView中必须要解析这些自定义属性。
public TabView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
// 加载布局
inflate(context, R.layout.tab_layout, this);
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.TabView);
for (int i = 0; i < a.getIndexCount(); i++) {
int attr = a.getIndex(i);
switch (attr) {
case R.styleable.TabView_tabColor:
// 获取标题和轮廓最终的着色
mTargetColor = a.getColor(attr, DEFAULT_TAB_TARGET_COLOR);
break;
case R.styleable.TabView_tabImage:
// 获取轮廓图
mNormalDrawable = a.getDrawable(attr);
break;
case R.styleable.TabView_tabSelectedImage:
// 获取选中图
mSelectedDrawable = a.getDrawable(attr);
break;
case R.styleable.TabView_tabTitle:
// 获取标题
mTitle = a.getString(attr);
break;
}
}
a.recycle();
}
自定义属性解析完毕后,就需要给用这些属性值给控件进行初始化。View的onFinishInflate()方法代表布局加载完成,因此在这里获取控件,并进行初始化。
@Override
protected void onFinishInflate() {
super.onFinishInflate();
// 1.设置标题,默认着色为黑色
mTitleView = findViewById(R.id.tab_title);
mTitleView.setTextColor(DEFAULT_TAB_COLOR);
mTitleView.setText(mTitle);
// 2.设置轮廓图片,不透明,默认着色为黑色
mNormalImageView = findViewById(R.id.tab_image);
mNormalDrawable.setTint(DEFAULT_TAB_COLOR);
mNormalDrawable.setAlpha(255);
mNormalImageView.setImageDrawable(mNormalDrawable);
// 3.设置选中图片,透明,默认着色为黑色
mSelectedImageView = findViewById(R.id.tab_selected_image);
mSelectedDrawable.setAlpha(0);
mSelectedImageView.setImageDrawable(mSelectedDrawable);
}
标题设置了一个默认颜色DEFAULT_TAB_COLOR,是黑色。同样,也为轮廓图的轮廓设置黑色。轮廓图的透明度初始为255,也就是完全可见,而选中图的透明度设置为0,也就是完全不可见。所有这一切就是动画的初始状态。控制动画进度在前面的讲解动画的原理的时候说到一个事情,TabView需要使用ViewPager滑动进度值来控制动画的进度,因此还要为TabView定义一个接收进度值的方法。
/**
-
根据进度值进行变色和透明度处理。
-
@param percentage 进度值,取值[0, 1]。
*/
public void setXPercentage(float percentage) {
if (percentage < 0 || percentage > 1) {
return;
}
// 1. 颜色变换
int finalColor = evaluate(percentage, DEFAULT_TAB_COLOR, mTargetColor);
mTitleView.setTextColor(finalColor);
mNormalDrawable.setTint(finalColor);
// 2. 透明度变换
if (percentage >= 0.5 && percentage <= 1) {
// 原理如下
// 进度值: 0.5 ~ 1
// 透明度: 0 ~ 1
// 公式: percentage - 1 = (alpha - 1) * 0.5
int alpha = (int) Math.ceil(255 * ((percentage - 1) * 2 + 1));
mNormalDrawable.setAlpha(255 - alpha);
mSelectedDrawable.setAlpha(alpha);
} else {
mNormalDrawable.setAlpha(255);
mSelectedDrawable.setAlpha(0);
}
// 3. 更新UI
invalidateUI();
}
尾声
面试成功其实都是必然发生的事情,因为在此之前我做足了充分的准备工作,不单单是纯粹的刷题,更多的还会去刷一些Android核心架构进阶知识点,比如:JVM、高并发、多线程、缓存、热修复设计、插件化框架解读、组件化框架设计、图片加载框架、网络、设计模式、设计思想与代码质量优化、程序性能优化、开发效率优化、设计模式、负载均衡、算法、数据结构、高级UI晋升、Framework内核解析、Android组件内核等。
不仅有学习文档,视频+笔记提高学习效率,还能稳固你的知识,形成良好的系统的知识体系。这里,笔者分享一份从架构哲学的层面来剖析的视频及资料分享给大家梳理了多年的架构经验,筹备近6个月最新录制的,相信这份视频能给你带来不一样的启发、收获。
Android进阶学习资料库
一共十个专题,包括了Android进阶所有学习资料,Android进阶视频,Flutter,java基础,kotlin,NDK模块,计算机网络,数据结构与算法,微信小程序,面试题解析,framework源码!
大厂面试真题
PS:之前因为秋招收集的二十套一二线互联网公司Android面试真题 (含BAT、小米、华为、美团、滴滴)和我自己整理Android复习笔记(包含Android基础知识点、Android扩展知识点、Android源码解析、设计模式汇总、Gradle知识点、常见算法题汇总。)
《2017-2021字节跳动Android面试历年真题解析》
《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》,点击传送门,即可获取!
…(img-cWyhyovm-1715421776044)]
大厂面试真题
PS:之前因为秋招收集的二十套一二线互联网公司Android面试真题 (含BAT、小米、华为、美团、滴滴)和我自己整理Android复习笔记(包含Android基础知识点、Android扩展知识点、Android源码解析、设计模式汇总、Gradle知识点、常见算法题汇总。)
[外链图片转存中…(img-F0L8MHDM-1715421776045)]
《2017-2021字节跳动Android面试历年真题解析》
[外链图片转存中…(img-HKkjrPxj-1715421776046)]
《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》,点击传送门,即可获取!