做一个带滑动动画的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<itemWidth){
                itemScale[i]= (float) (-0.5*deltaX/itemWidth+1);/*当距离小于一个item的宽度时调整item的缩放系数*/
            }
            else itemScale[i]=0.5f;/*非选中的item的缩放系数固定为0.5*/
            
            /*对item的大小进行缩放*/
            View childImageView = getChildAt(i);
            childImageView.setScaleX(itemScale[i]);
            childImageView.setScaleY(itemScale[i]);
            View childTextView = getChildAt(itemCount+i);
            childTextView.setScaleX(itemScale[i]);
            childTextView.setScaleY(itemScale[i]);
        }
        super.onDraw(canvas);

    }

我用了几个数组来记录每个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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,204评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,091评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,548评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,657评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,689评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,554评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,302评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,216评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,661评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,851评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,977评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,697评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,306评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,898评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,019评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,138评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,927评论 2 355

推荐阅读更多精彩内容

  • Core Animation是直接作用在CALayer上的(并非UIView上)非常强大的跨Mac OS X和iO...
    阳明先生_X自主阅读 1,255评论 0 8
  • 【Android 动画】 动画分类补间动画(Tween动画)帧动画(Frame 动画)属性动画(Property ...
    Rtia阅读 6,159评论 1 38
  • 倒序的思维
    李永开阅读 2,247评论 2 1
  • 7.20路静娟【通读一本书】-《男人来自火星,女人来自金星》 正文:男人要学会给予女人更多,关键的一步,就是知道错...
    台一DDM路静娟阅读 345评论 0 0
  • 岁月的皱纹不过是风月无两 风中尚还裹协着青春的气息 落日的余辉已带着迟暮沉沉睡去 那光芒在我的指尖织就一层金黄 我...
    茶绒阅读 204评论 0 0