1.仿饭局狼人杀底部动画导航栏

首先看看效果图
ezgif.com-video-to-gif.gif

以前做这样的导航栏控件使用继承View实现的,导航个数写死例如5个,不断的计算变化前后的各个区域的Rect,重写onTouchEvent,根据点击点不同进行重新绘制,以前能实现的效果,只是图片的上移和长图的动态移动,动画效果并不理想。在使用的过程中,如果要增加导航图片数量,计算很麻烦。因此一直想抽空,改一改这个自定义控件。

上面效果图用的方法是:1.继承自ViewGroup,2.利用属性动画。
首先分析实现原理:从效果图可以看出基本的图片包括导航图片(选中/未选中)、整体背景图片、选中的覆盖图片、各图标之间的分割图片,具体如下。


1.仿饭局狼人杀底部动画导航栏_第1张图片
icon_home_game_normal.png
1.仿饭局狼人杀底部动画导航栏_第2张图片
icon_home_game_pressed.png
1.仿饭局狼人杀底部动画导航栏_第3张图片
img_com_tab_bg.png
img_com_tab_pressed_new_x.png
img_home_bottom_item_line.png

我进行这样自定义控件设置的时候,第一步首先把背景、分割图片、选中后的覆盖图片绘制出来。因此先自定义这三个属性 attrs.xml文件



  
      
      
      
  

上面的属性值分别代表背景、选中的覆盖图、分割图。
在测试的布局文件中需要设定这三个属性值




       

在自定义的ViewGroup中绘制

public class BottomWolfKillNavigation extends ViewGroup implements View.OnClickListener{

    //导航栏背景图片
    private int backgroundImageRes;
    //导航栏选择后的前图
    private int frontImageRes;
    //导航栏的分割图
    private int cutImageRes;

    private Paint mPaint;

  public BottomWolfKillNavigation(Context context) {
        this(context,null);
    }

    public BottomWolfKillNavigation(Context context, AttributeSet attrs) {
        //super(context, attrs);
        this(context,attrs,0);

    }

    public BottomWolfKillNavigation(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        this.context = context;
        parseAttrs(context, attrs);

        initView();
    }
//获取参数值
    private void parseAttrs(Context context,AttributeSet attrs){

        if (attrs!=null){
            TypedArray typedArray = context.getTheme().obtainStyledAttributes(attrs, R.styleable.BottomWolfKillNavigation,0,0);
            backgroundImageRes = typedArray.getResourceId(R.styleable.BottomWolfKillNavigation_backgroundimage,0);
            frontImageRes = typedArray.getResourceId(R.styleable.BottomWolfKillNavigation_frontimage,0);
            cutImageRes = typedArray.getResourceId(R.styleable.BottomWolfKillNavigation_cutimage,0);
            typedArray.recycle();
        }
    }

    private void initView(){
        mPaint = new Paint();
        mPaint.setAntiAlias(false);
//ViewGroup默认不会调用onDraw方法设置背景色后会调用
        setBackgroundColor(Color.TRANSPARENT);
    }
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        mPaint.setColor(Color.TRANSPARENT);
        Rect transRect = new Rect(0,0,ScreenUtils.getScreenWidth(),SizeUtils.dp2px(20));
        canvas.drawRect(transRect,mPaint);
        mPaint.reset();
        Rect backgroundRect = new Rect(0,SizeUtils.dp2px(20),ScreenUtils.getScreenWidth(),SizeUtils.dp2px(80));
        Bitmap background = BitmapFactory.decodeResource(getResources(),backgroundImageRes);
        canvas.drawBitmap(background,null,backgroundRect,mPaint);
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
}
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        measureChildren(widthMeasureSpec, heightMeasureSpec);
        //设置导航栏的宽度、高度
        setMeasuredDimension(ScreenUtils.getScreenWidth(), SizeUtils.dp2px(80));
        //需要适配虚拟的导航按键
        if (ScreenUtils.isPortrait()){

        }else{

        }
    }

}

通过以上可以画出背景,上面用到的获取屏幕宽度的工具类是在项目中引用第三方工具类implementation "com.blankj:utilcode:1.12.5"。链接地址
http://www.apkbus.com/blog-901770-76998.html。
为了能够设置每个导航控件的定义一个Item存选中图片与未选中图标

public class BottomImageItem {
    private int unSelectedImageRes;//没有选择的图片
    private int selectedImageRes;//选择后的图片

    public BottomImageItem(@DrawableRes int unSelectedImageRes, @DrawableRes int selectedImageRes) {
        this.unSelectedImageRes = unSelectedImageRes;
        this.selectedImageRes = selectedImageRes;
    }

    public int getUnSelectedImageRes() {
        return unSelectedImageRes;
    }

    public void setUnSelectedImageRes(int unSelectedImageRes) {
        this.unSelectedImageRes = unSelectedImageRes;
    }

    public int getSelectedImageRes() {
        return selectedImageRes;
    }

    public void setSelectedImageRes(int selectedImageRes) {
        this.selectedImageRes = selectedImageRes;
    }
}

自定义控件的重写方法的顺序 onMeasure onLayout onDraw
onMeasure中已经指定宽高,高度为80dp,其中0-20dp透明,20-80dp是背景图。
为了能够动态设置导航图标的数量,需要有个集合还管理导航图标

//存导航栏每个Item
    private ArrayList bottomImageItemsList = new ArrayList<>();
   //存分割线
    private ArrayList cutImageViewList = new ArrayList<>();
    //存导航图标
    private ArrayList iconImageViewList = new ArrayList<>();

//暴露给外界的接口,用于添加导航图片
    public BottomWolfKillNavigation addBottomImageItem(BottomImageItem item){
        bottomImageItemsList.add(item);
        ImageView cutImage = new ImageView(context);
        this.addView(cutImage);
        if (iconImageViewList.size()>0){
            cutImageViewList.add(cutImage);
            Log.i("xsl","cutlinesize="+cutImageViewList.size());
        }

        ImageView imageView =  new ImageView(context);
//为了能设置监听获取点击的是哪个view
        imageView.setTag(bottomImageItemsList.size()-1);
        iconImageViewList.add(imageView);
        this.addView(imageView);
        return this;
    }

还需要暴露给外界设置该ViewGroup各个孩子的方法

    public void initialise() {

        itemSize = bottomImageItemsList.size();
        //导航栏图片数量+分割线数量+前面选择图片
        //分割线数量等于导航栏图片数量-1
        //前面选择图片数量等于1
        screenWidth = ScreenUtils.getScreenWidth();
        cutWidth = SizeUtils.dp2px(2);

        //除去分割线的宽度
        contentWidth = screenWidth-cutWidth*(itemSize-1);
        //平均每个导航栏的宽度
        everyWidth = (int)(contentWidth/(itemSize+0.5f));
        //被选中的导航栏的宽度
        longWidth = contentWidth-everyWidth*(itemSize-1);
        top = SizeUtils.dp2px(20);
        bottom = SizeUtils.dp2px(80);
        bigImageHalfWidth = SizeUtils.dp2px(30);
        smallImageHalfWidth = SizeUtils.dp2px(25);
        cutMoveLength = longWidth-everyWidth;

        Log.i("xsl","原始计算值"+longWidth+","+everyWidth);
        //drawContent();
        for(int i=0;i

经过上面两个方法其实,我进行设置绘制child的顺序是选中的背景图片、导航图片、分割图片、导航图片、分割图片……一开始我是把选中的背景图片放在最后绘制,结果图标被覆盖,色调变了。
其实onMeasure之后就应该讲onLayout,先贴源码

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {

        for (int i=0;i

目前的onlayout是按第0个位置被选中进行布局的。
下面的任务就是要在点击图标(给孩子设置监听)的时候让所有的组件按想要的方式动起来:
1.选中的背景图片移动
2.分割图片的移动
3.导航图片的缩放及移动

    @Override
    public void onClick(View v) {
        selectPosition = (Integer) v.getTag();
        if (selectPosition!=oldPosition){    
        iconImageViewList.get(oldPosition).setImageResource(bottomImageItemsList.get(oldPosition).getUnSelectedImageRes());
            //前景选中图片的动画
            frontImageAnimation();
        }
    }

前景选中图片的动画函数

    private void frontImageAnimation(){
        float begin = 0f;
        float length = 0f;
        float end = 0f;
        //总共移动距离
        length = (selectPosition-oldPosition)*(everyWidth+cutWidth);
        //左边的开始点
        begin = (oldPosition)*(everyWidth+cutWidth);
        end = begin+length;
        final ObjectAnimator animator = ObjectAnimator.ofFloat(frontImageView,"translationX",begin,end);
        animator.setDuration(500);
        animator.start();
        //设置动画监听移动的同时,分割图片、导航图片要变化
        animator.addListener(new Animator.AnimatorListener() {
            @Override
            public void onAnimationStart(Animator animation) {
//动画开始后要进行分割图片的动画下面有代码
//导航图片的动画

}
            @Override
            public void onAnimationEnd(Animator animation) {
//动画结束
                oldPosition = selectPosition;
                animator.removeAllListeners();

  //设置选中的图标  
 iconImageViewList.get(selectPosition).setImageResource(bottomImageItemsList.get(selectPosition).getSelectedImageRes());

            }

            @Override
            public void onAnimationCancel(Animator animation) {

            }

            @Override
            public void onAnimationRepeat(Animator animation) {

            }
});

}

那么在前景选中图片移动的同时,分割图片也要移动这里要注意到跨距离点击,分割图片是一个一个移动的。具体代码如下:

//用于分割线动画
                 AnimatorSet animatorSet = new AnimatorSet();
                 animatorSet.setDuration(500/(Math.abs(selectPosition-oldPosition)));


                List animatorList = new ArrayList<>();

                 if (oldPositionselectPosition;i--){
                         View x = cutImageViewList.get(i-1);
                         ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(x,"translationX",
                                 -cutMoveLength,0);
                         animatorList.add(objectAnimator);
                     }
                     animatorSet.playSequentially(animatorList);
                     animatorSet.start();
                     //这是图标缩放移动的函数
                     iconAnimationToLeft(oldPosition,selectPosition);
                 }

这里要注意设置的时间

animatorSet.setDuration(500/(Math.abs(selectPosition-oldPosition)));

启动动画的方式

animatorSet.playSequentially(animatorList);

还有重要的两个函数就是图标的移动和缩放

 iconAnimationToRight(oldPosition,selectPosition);
 iconAnimationToLeft(oldPosition,selectPosition);

我在分析缩放平移的时候首先是分析两个相邻的之间的缩放
一个图片的动画就包括XY缩放XY平移因此用AnimatorSet.playTogether方法实现,关键跨距离时,中间的经过的各个导航图标都要缩放,因此同样需要设置某个动画监听,结束动画后,继续回调该方法,具体源码

private void iconAnimationToRight(int i, int selectPosition){
                List animatorListSmallScale = new ArrayList<>();
                //用于图片平移缩放动画
                AnimatorSet animatorSetSmallScale = new AnimatorSet();
                animatorSetSmallScale.setDuration(500/(Math.abs(selectPosition-oldPosition)));
                ObjectAnimator objectAnimator1 = null;
                if (i==0) {
                    objectAnimator1 = ObjectAnimator.ofFloat(iconImageViewList.get(i),
                            "scaleX",
                            1f, 1f/1.2f);
                    ObjectAnimator objectAnimator2 = ObjectAnimator.ofFloat(iconImageViewList.get(i),
                            "scaleY",
                            1f, 1f/1.2f);
                    ObjectAnimator objectAnimator3 = ObjectAnimator.ofFloat(iconImageViewList.get(i),
                            "translationX",
                            0, -((longWidth - everyWidth) / 2));
                    ObjectAnimator objectAnimator4 = ObjectAnimator.ofFloat(iconImageViewList.get(i),
                            "translationY",
                            0,SizeUtils.dp2px(18));

                    ObjectAnimator objectAnimator5 = ObjectAnimator.ofFloat(iconImageViewList.get(i + 1),
                            "scaleX",
                            1f, 1.2f);
                    ObjectAnimator objectAnimator6 = ObjectAnimator.ofFloat(iconImageViewList.get(i + 1),
                            "scaleY",
                            1f, 1.2f);
                    ObjectAnimator objectAnimator7 = ObjectAnimator.ofFloat(iconImageViewList.get(i + 1),
                            "translationX",
                            0, -((longWidth - everyWidth) / 2));
                    ObjectAnimator objectAnimator8 = ObjectAnimator.ofFloat(iconImageViewList.get(i + 1),
                            "translationY",
                            0, -SizeUtils.dp2px(15));
                    animatorListSmallScale.add(objectAnimator1);
                    animatorListSmallScale.add(objectAnimator2);
                    animatorListSmallScale.add(objectAnimator3);
                    animatorListSmallScale.add(objectAnimator4);
                    animatorListSmallScale.add(objectAnimator5);
                    animatorListSmallScale.add(objectAnimator6);
                    animatorListSmallScale.add(objectAnimator7);
                    animatorListSmallScale.add(objectAnimator8);
                }else{
                    objectAnimator1 = ObjectAnimator.ofFloat(iconImageViewList.get(i),
                            "scaleX",
                            1.2f, 1f);
                    ObjectAnimator objectAnimator2 = ObjectAnimator.ofFloat(iconImageViewList.get(i),
                            "scaleY",
                            1.2f, 1f);
                    ObjectAnimator objectAnimator3 = ObjectAnimator.ofFloat(iconImageViewList.get(i),
                            "translationX",
                            -((longWidth - everyWidth) / 2),-(longWidth - everyWidth));
                    ObjectAnimator objectAnimator4 = ObjectAnimator.ofFloat(iconImageViewList.get(i),
                            "translationY",
                            -SizeUtils.dp2px(15),0);

                    ObjectAnimator objectAnimator5 = ObjectAnimator.ofFloat(iconImageViewList.get(i + 1),
                            "scaleX",
                            1f, 1.2f);
                    ObjectAnimator objectAnimator6 = ObjectAnimator.ofFloat(iconImageViewList.get(i + 1),
                            "scaleY",
                            1f, 1.2f);
                    ObjectAnimator objectAnimator7 = ObjectAnimator.ofFloat(iconImageViewList.get(i + 1),
                            "translationX",
                            0, -((longWidth - everyWidth) / 2));
                    ObjectAnimator objectAnimator8 = ObjectAnimator.ofFloat(iconImageViewList.get(i + 1),
                            "translationY",
                            0, -SizeUtils.dp2px(15));
                    animatorListSmallScale.add(objectAnimator1);
                    animatorListSmallScale.add(objectAnimator2);
                    animatorListSmallScale.add(objectAnimator3);
                    animatorListSmallScale.add(objectAnimator4);
                    animatorListSmallScale.add(objectAnimator5);
                    animatorListSmallScale.add(objectAnimator6);
                    animatorListSmallScale.add(objectAnimator7);
                    animatorListSmallScale.add(objectAnimator8);
                }
                animatorSetSmallScale.playTogether(animatorListSmallScale);
                animatorSetSmallScale.start();

                i++;
                final int x=i;
                final int selectflag = selectPosition;

                objectAnimator1.addListener(new Animator.AnimatorListener() {
                    @Override
                    public void onAnimationStart(Animator animation) {

                    }

                    @Override
                    public void onAnimationEnd(Animator animation) {
                        if (x

点击选中的左边同样的逻辑
为了能测试动态改变图标数量还需要暴露接口

    public void clear(){
        cutImageViewList.clear();
        iconImageViewList.clear();
        bottomImageItemsList.clear();
        oldPosition=selectPosition=0;
        removeAllViews();
    }

最后贴出测试代码

public class WolfActivity extends Activity implements View.OnClickListener{

    private BottomWolfKillNavigation navigation;

    private Button bt2;
    private Button bt3;
    private Button bt4;
    private Button bt5;

    @Override
    public void onClick(View v) {
        switch (v.getId()){
            case R.id.btn2:
                navigation.clear();
                navigation.addBottomImageItem(new BottomImageItem(R.drawable.icon_home_club_normal,R.drawable.icon_home_club_pressed))
                        .addBottomImageItem(new BottomImageItem(R.drawable.icon_home_game_normal,R.drawable.icon_home_game_pressed))
                        .initialise();
                break;
            case R.id.btn3:
                navigation.clear();
                navigation.addBottomImageItem(new BottomImageItem(R.drawable.icon_home_club_normal,R.drawable.icon_home_club_pressed))
                        .addBottomImageItem(new BottomImageItem(R.drawable.icon_home_game_normal,R.drawable.icon_home_game_pressed))
                        .addBottomImageItem(new BottomImageItem(R.drawable.icon_home_shop_normal,R.drawable.icon_home_shop_pressed))
                        .initialise();
                break;
            case R.id.btn4:
                navigation.clear();
                navigation.addBottomImageItem(new BottomImageItem(R.drawable.icon_home_club_normal,R.drawable.icon_home_club_pressed))
                        .addBottomImageItem(new BottomImageItem(R.drawable.icon_home_game_normal,R.drawable.icon_home_game_pressed))
                        .addBottomImageItem(new BottomImageItem(R.drawable.icon_home_shop_normal,R.drawable.icon_home_shop_pressed))
                        .addBottomImageItem(new BottomImageItem(R.drawable.icon_home_social_normal,R.drawable.icon_home_social_pressed))
                        .initialise();
                break;
            case R.id.btn5:
                navigation.clear();
                navigation.addBottomImageItem(new BottomImageItem(R.drawable.icon_home_club_normal,R.drawable.icon_home_club_pressed))
                        .addBottomImageItem(new BottomImageItem(R.drawable.icon_home_game_normal,R.drawable.icon_home_game_pressed))
                        .addBottomImageItem(new BottomImageItem(R.drawable.icon_home_shop_normal,R.drawable.icon_home_shop_pressed))
                        .addBottomImageItem(new BottomImageItem(R.drawable.icon_home_social_normal,R.drawable.icon_home_social_pressed))
                        .addBottomImageItem(new BottomImageItem(R.drawable.icon_home_watch_normal,R.drawable.icon_home_watch_pressed))
                        .initialise();
                break;
        }
    }

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Utils.init(getApplication());
        setContentView(R.layout.activity_wolf);

        navigation = (BottomWolfKillNavigation) findViewById(R.id.nav);
        navigation.addBottomImageItem(new BottomImageItem(R.drawable.icon_home_club_normal,R.drawable.icon_home_club_pressed))
                .addBottomImageItem(new BottomImageItem(R.drawable.icon_home_game_normal,R.drawable.icon_home_game_pressed))
                .addBottomImageItem(new BottomImageItem(R.drawable.icon_home_shop_normal,R.drawable.icon_home_shop_pressed))
                .addBottomImageItem(new BottomImageItem(R.drawable.icon_home_social_normal,R.drawable.icon_home_social_pressed))
                .addBottomImageItem(new BottomImageItem(R.drawable.icon_home_watch_normal,R.drawable.icon_home_watch_pressed))
                .initialise();

        bt2 = findViewById(R.id.btn2);
        bt3 = findViewById(R.id.btn3);
        bt4 = findViewById(R.id.btn4);
        bt5 = findViewById(R.id.btn5);
        bt2.setOnClickListener(this);
        bt3.setOnClickListener(this);
        bt4.setOnClickListener(this);
        bt5.setOnClickListener(this);
    }
}

你可能感兴趣的:(1.仿饭局狼人杀底部动画导航栏)