看到一个bottomBar的设计,感觉很好看,于是把它实现了出来
先看一下这个设计的效果是什么样的,原设计地址
[图片上传失败...(image-df3d71-1554883833921)]
可以看到这是一个常见的bottomBar
把它分解一下
- 一共有5个item,每个item的背景颜色不一样
- 点击item时,item是通过滑动来移动到相应的item上的,这个移动也不是简单的线性移动,而是带有粘性的.
- item移动时,item颜色的切换是有item之间过渡的,类似于加了一个遮罩
- 移到item时,item本身是伴随item的移动是有一个动画的.
根据我们的分解,一步一步解决问题
考虑到这是一个bottomBar,我选择了自定义ViewGroup来实现.因为用ViewGroup添加item会比较方便.
public class AnimationBottomBar extends ViewGroup {
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
}
}
另外item内的小动画我也选择用缩放的形式实现,所以个效果图会有一些出入
一 添加item
通常来说,一个item会有一个图标和简短的标题.
举个例子,就像是知乎,即刻下方的bottomBar一样
即刻的[图片上传失败...(image-41233e-1554883833921)]
知乎的[图片上传失败...(image-92efaa-1554883833921)]
所以一个item内有也要有一个图标和一个标题
添加item的时候要足够方便,使用代码添加是个不错的选择,类似于这样mAnimationBottomBar.addItem(item)
.
我创建了一个简单的BottomItem
类来包装item,
public class BottomItem {
int drawableRes;//图标资源
String title;//标题
public BottomItem(@DrawableRes int drawableRes,String title){
this.drawableRes=drawableRes;
this.title=title;
}
}
添加item之后,我将添加的BottomItem保存到一个list里
public AnimationBottomBar addItem(BottomItem bottomItem) {
mBottomItemArrayList.add(bottomItem);
return this;
}
添加item之后会返会对象本身,就可以继续.addItem()
了,就像这样
mAnimationBottomBar.addItem(new BottomItem(R.drawable.h, "zero"))
.addItem(new BottomItem(R.drawable.h, "one"))
.addItem(new BottomItem(R.drawable.h, "two"))
.addItem(new BottomItem(R.drawable.h, "four"))
.addItem(new BottomItem(R.drawable.h, "five"))
好了,现在已经添加了item,嗯?球都没得.运行没有显示出来,当然啦添加了之后需要添加到ViewGroup里,在经过onMeasure
和onLayout
之后才会显示出来
public void build() {
itemCount = mBottomItemArrayList.size();
itemWidth=getLayoutParams().width/itemCount;/*获得平均一个item的宽度,这里有个问题,因为这个时候还没有经过OnMeaSure(),width获取不到,在onMeasure里可以再次进行调整*/
for (BottomItem bottomItem : mBottomItemArrayList) {/*添加图标*/
ImageView imageView = new ImageView(mContext);
imageView.setImageResource(bottomItem.drawableRes);
addView(imageView, itemWidth, 20);
}
for (BottomItem bottomItem : mBottomItemArrayList) {/*添加标题/
TextView textView=new TextView(mContext);
textView.setTextSize(textSize);
textView.setText(bottomItem.title);
textView.setTextColor(textColor);
textView.setGravity(Gravity.CENTER);
addView(textView,itemWidth,20);
}
}
onMeasure(),遍历刚刚所有添加子View,通知它们测量自己的长宽
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
childCount = getChildCount();/*获得所有子View的数量*/
barWidth = getSize(300,widthMeasureSpec);//bottombar的宽度
barHeight = getSize(300,heightMeasureSpec);//--的高度
for (int i = 0; i < childCount; i++) {
View childView = getChildAt(i);
measureChild(childView, widthMeasureSpec, heightMeasureSpec);
childView.getLayoutParams().width=itemWidth;/*调整子view的宽度*/
}
}
onLayout(),确定所有的子View应该在的位置
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
for (int i = 0; i < itemCount; i++) {/*遍历每一个item,放置item的位置*/
itemCenterX[i] = (int) (itemWidth * (i + 0.5));/*记录每个item的中心位置*/
View childImageView = getChildAt(i);
childImageView.layout(itemWidth * i, 0, itemWidth * (i + 1), 100);//放置图标,
View childTextView=getChildAt(itemCount+i);
childTextView.layout(itemWidth * i+childTextView.getWidth()/4,100,itemWidth * (i + 1),barHeight);/*放置标题*/
}
}
此时的样子应该是这样的
[图片上传失败...(image-4e06a1-1554883833921)]
二 添加背景颜色
你可能会想到用setBackGroundColor()
来设置背景颜色,不过不要忘了,我们这个是要实现动画效果的,虽然使用setBackGroundColor()
也能实现,但是要复杂一些.我决定使用OnDraw()
画出来,在ViewGroup里默认是不调用OnDraw()
的具体原因见这里解决方法也很简单
如果我们要重写一个ViweGroup的onDraw方法,有两种方法:
1,在构造函数里面,给其设置一个颜色,如#00000000。
2,在构造函数里面,调用setWillNotDraw(false),去掉其WILL_NOT_DRAW flag。
我选择了第二个方法,因为我们要自己实现背景.
@Override
protected void onDraw(Canvas canvas) {
/*绘制item颜色*/
for (int i = 0; i < 5; i++) {
mPaint.setColor(itemcolors[i]);
canvas.drawRect(itemWidth * i, 0, itemWidth * (i + 1), barHeight, mPaint);
canvas.save();
}
/*画出背景,两个长方形*/
mPaint.setColor(backGroundColor);
canvas.drawRect(0, 0, itemMoveLeft, barHeight, mPaint);
canvas.drawRect(itemMoveRight, 0, itemWidth * 5, barHeight, mPaint);
canvas.save();
super.onDraw(canvas);
}
这里我分了两部分来画,一是每个item的背景颜色,二是整体的背景颜色,注意画的先后顺序哦,我为了实现item的移动,把item部分画在下层,把背景画在了上层,通过改变背景来实现item的移动效果.
这时候的效果是这样的
[图片上传失败...(image-d56596-1554883833921)]
三 实现动画
注意这里的动画其实分为两个部分,两部分是同时进行的
- item的移动动画
- item的缩放动画
@Override
protected void onDraw(Canvas canvas) {
/绘制item颜色*/
for (int i = 0; i < 5; i++) {
mPaint.setColor(itemcolors[i]);
canvas.drawRect(itemWidth * i, 0, itemWidth * (i + 1), barHeight, mPaint);
canvas.save();
}
/*画出背景,两个长方形*/
mPaint.setColor(backGroundColor);
canvas.drawRect(0, 0, itemMoveLeft, barHeight, mPaint);
canvas.drawRect(itemMoveRight, 0, itemWidth * 5, barHeight, mPaint);
canvas.save();
/*遍历每个item位置,画出需要移动和缩放的item*/
for (int i = 0; i < itemCount; i++) {
int deltaX=Math.abs(itemMoveCenter-itemCenterX[i]);/*获得当前item移动中心点和item固定中心点的距离*/
if (deltaX
我用了几个数组来记录每个item的固定中心位置,每个item的颜色,每个item的缩放系数.
缩放系数这里,默认的未选中item的缩放系数是0.5,选中的item的缩放系数就是1.0,移动的时候,越靠近选中的item就这个系数就越大.
既然是动画我们肯定要让她动起来,我继承了Animation
类实现了自己的BottomAnimation
类
private class BottomAnimation extends Animation {
@Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
super.applyTransformation(interpolatedTime, t);
int position = selectIndex - selectLastIndex;
/*判断不同方向的移动*/
if (position < 0) {/*向左滑动*/
itemMoveRight = (int) (itemMoveLastRight + interpolatedTime * itemWidth * position);
itemMoveLeft = (int) (itemMoveLastLeft + setFirst(interpolatedTime) * itemWidth * position);
itemMoveCenter = (int) (itemMoveLastRight + interpolatedTime * itemWidth * position) -itemWidth / 2;/*记录中心点移动的位置*/
} else {/*向右滑动*/
itemMoveRight = (int) (itemMoveLastRight + setFirst(interpolatedTime) * itemWidth * position);
itemMoveLeft = (int) (itemMoveLastLeft + interpolatedTime * itemWidth * position);
itemMoveCenter = (int) (itemMoveLastLeft + interpolatedTime * itemWidth * position) + itemWidth / 2;/*记录中心点移动的位置*/
}
postInvalidate();/*更新画面*/
}
/*为了实现果冻效果,先移动的一侧要有快速效果*/
private float setFirst(float interpolatedTime) {
return (float) Math.sin(interpolatedTime * 0.5 * Math.PI);
}
}
在判断到有点击事件之后,启动这个动画就ok了
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
int action = ev.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN:
touchDownX = ev.getX();
break;
case MotionEvent.ACTION_UP:
if (ev.getX() / itemWidth == touchDownX / itemWidth) {
selectIndex = (int) (ev.getX() / itemWidth);
/*点击时开始动画*/
startAnimation(mBottomAnimation);
}
break;
}
return true;
}
最后的效果是这个样子的
[图片上传失败...(image-cabe4c-1554883833921)]
最后完整的的代码在我的github