高仿微信底部导航栏动画

然后,在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();

}

当加载完布局后,需要为各个控件设置相应的属性

@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);

}

第二步中,为轮廓图调用了Drawable.setTint()方法为轮廓着色,默认着色为黑色。

Drawable.setTint() 其实就是利用 PorterDuff.Mode.DST_IN 来进行颜色混合。

然后我还需要一个接收进度值(范围为0.f 到 1.f)的方法,从而利用这个进度值做动画

/**

  • 根据进度值进行变色和透明度处理。

  • @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();

}

第一步是根据进度值来计算颜色值。在属性动画中,有一个ArgbEvaluator类,这是一个对颜色做动画的类,它里面有一个方法如下

public Object evaluate(float fraction, Object startValue, Object endValue) {

int startInt = (Integer) startValue;

float startA = ((startInt >> 24) & 0xff) / 255.0f;

float startR = ((startInt >> 16) & 0xff) / 255.0f;

float startG = ((startInt >> 8) & 0xff) / 255.0f;

float startB = ( startInt & 0xff) / 255.0f;

int endInt = (Integer) endValue;

float endA = ((endInt >> 24) & 0xff) / 255.0f;

float endR = ((endInt >> 16) & 0xff) / 255.0f;

float endG = ((endInt >> 8) & 0xff) / 255.0f;

float endB = ( endInt & 0xff) / 255.0f;

// convert from sRGB to linear

startR = (float) Math.pow(startR, 2.2);

startG = (float) Math.pow(startG, 2.2);

startB = (float) Math.pow(startB, 2.2);

endR = (float) Math.pow(endR, 2.2);

endG = (float) Math.pow(endG, 2.2);

endB = (float) Math.pow(endB, 2.2);

// compute the interpolated color in linear space

float a = startA + fraction * (endA - startA);

float r = startR + fraction * (endR - startR);

float g = startG + fraction * (endG - startG);

float b = startB + fraction * (endB - startB);

// convert back to sRGB in the [0…255] range

a = a * 255.0f;

r = (float) Math.pow(r, 1.0 / 2.2) * 255.0f;

g = (float) Math.pow(g, 1.0 / 2.2) * 255.0f;

b = (float) Math.pow(b, 1.0 / 2.2) * 255.0f;

return Math.round(a) << 24 | Math.round® << 16 | Math.round(g) << 8 | Math.round(b);

}

熟悉属性动画的应该知道,参数float fraction的取值范围为0到1,所以可以把这个方法拷贝过来使用。计算出颜色值后,就可以对标题和轮廓图着色了。第二步,按照之前说的动画原理,要利用进度值计算透明度,然后分别对轮廓图和选中图设置透明度。透明度的计算原理已经在注释中写清楚了,这里不再赘述。最后就是更新UI了。

与ViewPager联动

一切准备就绪,就等一个ViewPager的进度值

mViewPager.addOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() {

@Override

public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {

}

});

既然想知道ViewPager如何提供进度值,那就必须了解onPageScrolled方法的几个参数的意思,首先从源码的注释中进行了解

/**

  • This method will be invoked when the current page is scrolled, either as part

  • of a programmatically initiated smooth scroll or a user initiated touch scroll.

  • @param position Position index of the first page currently being displayed.

  •             Page position+1 will be visible if positionOffset is nonzero.
    
  • @param positionOffset Value from [0, 1) indicating the offset from the page at position.

  • @param positionOffsetPixels Value in pixels indicating the offset from position.

*/

void onPageScrolled(int position, float positionOffset, int positionOffsetPixels);

从注释中可以看出,onPageScrolled方法是在滑动的时候调用,参数position代表当前显示的页面,其实这很容易产生误解,无论是从左边往右边滑动,还是从右边往左边滑动,position始终代表左边的页面,那么position + 1始终代表右边的页面。参数positionOffset代表滑动的进度值,并且还有很重要一点,大部分人都会忽略,如果参数positionOffset为非零值,那么右边的页面可见,也就是说,如果positionOffset的值是零,那么代表右边的页面是不可见的,这一点会在代码中体现出来。既然已经对参数有所了解,那么现在来看看实现

public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {

// 左边View进行动画

mTabViews.get(position).setXPercentage(1 - positionOffset);

// 如果positionOffset非0,那么就代表右边的View可见,也就说明需要对右边的View进行动画

if (positionOffset > 0) {

mTabViews.get(position + 1).setXPercentage(positionOffset);

}

更多学习和讨论,欢迎加入我们!

有许多来自一线的技术大牛,也有在小厂或外包公司奋斗的码农,我们致力打造一个平等,高质量的Android交流圈子,不一定能短期就让每个人的技术突飞猛进,但从长远来说,眼光,格局,长远发展的方向才是最重要的。

这里有2000+小伙伴,让你的学习不寂寞~·
《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》点击传送门,即可获取!
动画

if (positionOffset > 0) {

mTabViews.get(position + 1).setXPercentage(positionOffset);

}

[外链图片转存中…(img-WgUOrqbb-1715433450592)]

更多学习和讨论,欢迎加入我们!

有许多来自一线的技术大牛,也有在小厂或外包公司奋斗的码农,我们致力打造一个平等,高质量的Android交流圈子,不一定能短期就让每个人的技术突飞猛进,但从长远来说,眼光,格局,长远发展的方向才是最重要的。

这里有2000+小伙伴,让你的学习不寂寞~·
《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》点击传送门,即可获取!

  • 17
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值