做一个带滑动动画的bottomBar


看到一个bottomBar的设计,感觉很好看,于是把它实现了出来

先看一下这个设计的效果是什么样的,原设计地址
[图片上传失败...(image-df3d71-1554883833921)]

可以看到这是一个常见的bottomBar
把它分解一下

  1. 一共有5个item,每个item的背景颜色不一样
  2. 点击item时,item是通过滑动来移动到相应的item上的,这个移动也不是简单的线性移动,而是带有粘性的.
  3. item移动时,item颜色的切换是有item之间过渡的,类似于加了一个遮罩
  4. 移到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里,在经过onMeasureonLayout之后才会显示出来

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)]

三 实现动画

注意这里的动画其实分为两个部分,两部分是同时进行的

  1. item的移动动画
  2. 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

你可能感兴趣的:(做一个带滑动动画的bottomBar)