澳门新葡萄京官网首页 6

Android开发中利用ObjectAnimator实现ArcMenu

本文由码农网 –
苏耀东原创,转载请看清文末的转载要求,欢迎参与我们的付费投稿计划!

利用ObjectAnimator实现ArcMenu

本文介绍利用ObjectAnimator简单地实现ArcMenu,直接使用本文的ArcMenu类即可快捷地实现菜单功能。

该文章笔记代码是从慕课网自定义view课程中学习到的,有兴趣的同学可以学习一下

本文介绍利用ObjectAnimator简单地实现ArcMenu,直接使用本文的ArcMenu类即可快捷地实现菜单功能。

澳门新葡萄京官网首页,最终使用效果

先看下最终的使用效果:

private int[] imageRes = {R.id.img_menu, R.id.img_menu1, R.id.img_menu2, R.id.img_menu3, R.id.img_menu4, R.id.img_menu5};
private ArcMenu arcMenu;
...
//初始化,参数为资源图片id
 arcMenu = new ArcMenu(this, imageRes);

//点击事件,这边使用了annotation,直接使用findViewById然后设置监听事件也可以
    @Click
    public void img_menu() {
        mylog.d(" @Click img_menu");
        arcMenu.switchMenu();
    }

    @Click
    public void img_menu1() {
        arcMenu.clickItem();
        mylog.d(" @Click img_menu1");
    }

    @Click
    public void img_menu2() {
        arcMenu.clickItem();
        mylog.d(" @Click img_menu2");
    }

    @Click
    public void img_menu3() {
        arcMenu.clickItem();
        mylog.d(" @Click img_menu3");
    }

    @Click
    public void img_menu4() {
        arcMenu.clickItem();
        mylog.d(" @Click img_menu4");
    }

    @Click
    public void img_menu5() {
        arcMenu.clickItem();
        mylog.d(" @Click img_menu5");
    }

布局文件中,将需要用到的图片放在同一位置。

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/widget33"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:background="#262a34">

    <ImageView
        android:id="@+id/img_menu"
        android:layout_width="50dp"
        android:layout_height="50dp"
        android:layout_alignParentBottom="true"
        android:layout_alignParentRight="true"
        android:layout_marginRight="10dp"
        android:layout_marginBottom="10dp"
        android:src="@drawable/menu_add" />

    <ImageView
        android:id="@+id/img_menu1"
        android:visibility="gone"
        android:layout_width="50dp"
        android:layout_height="50dp"
        android:layout_alignParentBottom="true"
        android:layout_alignParentRight="true"
        android:layout_marginRight="10dp"
        android:layout_marginBottom="10dp"
        android:src="@drawable/float_on" />

    <ImageView
        android:id="@+id/img_menu2"
        android:visibility="gone"
        android:layout_width="50dp"
        android:layout_height="50dp"
        android:layout_alignParentBottom="true"
        android:layout_alignParentRight="true"
        android:layout_marginRight="10dp"
        android:layout_marginBottom="10dp"
        android:src="@drawable/float_on" />

    <ImageView
        android:id="@+id/img_menu3"
        android:visibility="gone"
        android:layout_width="50dp"
        android:layout_height="50dp"
        android:layout_alignParentBottom="true"
        android:layout_alignParentRight="true"
        android:layout_marginRight="10dp"
        android:layout_marginBottom="10dp"
        android:src="@drawable/float_on" />

    <ImageView
        android:id="@+id/img_menu4"
        android:visibility="gone"
        android:layout_width="50dp"
        android:layout_height="50dp"
        android:layout_alignParentBottom="true"
        android:layout_alignParentRight="true"
        android:layout_marginRight="10dp"
        android:layout_marginBottom="10dp"
        android:src="@drawable/float_on" />

    <ImageView
        android:id="@+id/img_menu5"
        android:visibility="gone"
        android:layout_width="50dp"
        android:layout_height="50dp"
        android:layout_alignParentBottom="true"
        android:layout_alignParentRight="true"
        android:layout_marginRight="10dp"
        android:layout_marginBottom="10dp"
        android:src="@drawable/float_on" />

</RelativeLayout>

下面为截图;

澳门新葡萄京官网首页 1

ArcMenu

我们在学习动画的时候以前总是用喜欢用Animation的一些子类进行动画的创建或者AnimationSet的集合进行动画的合并,到最后发现,API的耦合性使我并不是特别喜欢用那些对象。

最终使用效果

先看下最终的使用效果:

private int[] imageRes = {R.id.img_menu, R.id.img_menu1, R.id.img_menu2, R.id.img_menu3, 
R.id.img_menu4, R.id.img_menu5};
private ArcMenu arcMenu;
...
//初始化,参数为资源图片id
 arcMenu = new ArcMenu(this, imageRes);

//点击事件,这边使用了annotation,直接使用findViewById然后设置监听事件也可以
    @Click
    public void img_menu() {
        mylog.d(" @Click img_menu");
        arcMenu.switchMenu();
    }

    @Click
    public void img_menu1() {
        arcMenu.clickItem();
        mylog.d(" @Click img_menu1");
    }

    @Click
    public void img_menu2() {
        arcMenu.clickItem();
        mylog.d(" @Click img_menu2");
    }

    @Click
    public void img_menu3() {
        arcMenu.clickItem();
        mylog.d(" @Click img_menu3");
    }

    @Click
    public void img_menu4() {
        arcMenu.clickItem();
        mylog.d(" @Click img_menu4");
    }

    @Click
    public void img_menu5() {
        arcMenu.clickItem();
        mylog.d(" @Click img_menu5");
    }

布局文件中,将需要用到的图片放在同一位置。

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/widget33"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:background="#262a34">

    <ImageView
        android:id="@+id/img_menu"
        android:layout_width="50dp"
        android:layout_height="50dp"
        android:layout_alignParentBottom="true"
        android:layout_alignParentRight="true"
        android:layout_marginRight="10dp"
        android:layout_marginBottom="10dp"
        android:src="@drawable/menu_add" />

    <ImageView
        android:id="@+id/img_menu1"
        android:visibility="gone"
        android:layout_width="50dp"
        android:layout_height="50dp"
        android:layout_alignParentBottom="true"
        android:layout_alignParentRight="true"
        android:layout_marginRight="10dp"
        android:layout_marginBottom="10dp"
        android:src="@drawable/float_on" />

    <ImageView
        android:id="@+id/img_menu2"
        android:visibility="gone"
        android:layout_width="50dp"
        android:layout_height="50dp"
        android:layout_alignParentBottom="true"
        android:layout_alignParentRight="true"
        android:layout_marginRight="10dp"
        android:layout_marginBottom="10dp"
        android:src="@drawable/float_on" />

    <ImageView
        android:id="@+id/img_menu3"
        android:visibility="gone"
        android:layout_width="50dp"
        android:layout_height="50dp"
        android:layout_alignParentBottom="true"
        android:layout_alignParentRight="true"
        android:layout_marginRight="10dp"
        android:layout_marginBottom="10dp"
        android:src="@drawable/float_on" />

    <ImageView
        android:id="@+id/img_menu4"
        android:visibility="gone"
        android:layout_width="50dp"
        android:layout_height="50dp"
        android:layout_alignParentBottom="true"
        android:layout_alignParentRight="true"
        android:layout_marginRight="10dp"
        android:layout_marginBottom="10dp"
        android:src="@drawable/float_on" />

    <ImageView
        android:id="@+id/img_menu5"
        android:visibility="gone"
        android:layout_width="50dp"
        android:layout_height="50dp"
        android:layout_alignParentBottom="true"
        android:layout_alignParentRight="true"
        android:layout_marginRight="10dp"
        android:layout_marginBottom="10dp"
        android:src="@drawable/float_on" />

</RelativeLayout>

下面为截图;

澳门新葡萄京官网首页 2

具体实现

初始化,通过imageRes的数量设置相邻两个图标之间的角度,同时将imageView加入imageViewList中,方便后面使用

public class ArcMenu {
    private Activity context;
    private int[] imageRes;
    private List<ImageView> imageViewList = new ArrayList<>();
    private boolean isShowMenu = false;
    int radius = 180;
    double angle;

    public ArcMenu(Activity context, int[] imageRes) {
        angle = Math.PI / 2 / (imageRes.length - 2);
        radius = Tool.dip2px(context, radius);
        this.context = context;
        this.imageRes = imageRes;
        for (int imagRe : imageRes) {
            ImageView imageView = (ImageView) context.findViewById(imagRe);
            imageViewList.add(imageView);
        }
    }
}

菜单弹出动画,使用ObjectAnimator,对每一个图标进行平移操作,第0个图标为菜单开关,加入旋转动画。

private void openMenu() {
        isShowMenu = true;
        setItemVisible(true);
        ObjectAnimator animator1;
        ObjectAnimator animator2;
        List<ObjectAnimator> objectAnimators = new ArrayList<>();
        AnimatorSet set = new AnimatorSet();

        for (int i = 1; i < imageRes.length; i++) {
            animator1 = ObjectAnimator.ofFloat(imageViewList.get(i), "translationX", (float) (-radius * Math.sin(angle * (i-1))));
            animator2 = ObjectAnimator.ofFloat(imageViewList.get(i), "translationY", (float) (-radius * Math.cos(angle * (i-1))));
            objectAnimators.add(animator1);
            objectAnimators.add(animator2);
        }

        for (int i = 0; i < objectAnimators.size(); i++) {
            set.playTogether(objectAnimators.get(i));
        }
        set.setDuration(200);
        set.start();
        //第0个图标,菜单图标,加入动画
        ObjectAnimator.ofFloat(imageViewList.get(0),"rotation",0,135f).setDuration(200).start();
    }

同理,关闭菜单

private void closeMenu() {
        isShowMenu = false;
        ObjectAnimator animator1 = null;
        ObjectAnimator animator2;
        List<ObjectAnimator> objectAnimators = new ArrayList<>();
        AnimatorSet set = new AnimatorSet();

        for (int i = 1; i < imageRes.length; i++) {
            animator1 = ObjectAnimator.ofFloat(imageViewList.get(i), "translationX",  0);
            animator2 = ObjectAnimator.ofFloat(imageViewList.get(i), "translationY",  0);
            objectAnimators.add(animator1);
            objectAnimators.add(animator2);
        }

        animator1.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                setItemVisible(false);
                super.onAnimationEnd(animation);
            }
        });

        for (int i = 0; i < objectAnimators.size(); i++) {
            set.playTogether(objectAnimators.get(i));
        }

        set.setDuration(200);
        set.start();
        ObjectAnimator.ofFloat(imageViewList.get(0),"rotation",135f,0).setDuration(200).start();
    }

切换菜单开关

 public void switchMenu() {
        if (isShowMenu) {
            closeMenu();
        } else {
            openMenu();
        }
    }

图标被点击后,将所有子图标隐藏,同时调用closMenu(),将图片移回原处。

public void clickItem() {
        setItemVisible(false);
        closeMenu();
    }

    private void setItemVisible(boolean isVisible) {
        for (int i = 1; i < imageRes.length; i++)  {
            if (isVisible) {
                imageViewList.get(i).setVisibility(View.VISIBLE);
            } else {
                imageViewList.get(i).setVisibility(View.GONE);
            }
        }
    }

今天看了大神的讲解视频,发现了几个比较好用的动画属性集,下面我将从基础开始逐一介绍。

具体实现

初始化,通过imageRes的数量设置相邻两个图标之间的角度,同时将imageView加入imageViewList中,方便后面使用

public class ArcMenu {
    private Activity context;
    private int[] imageRes;
    private List<ImageView> imageViewList = new ArrayList<>();
    private boolean isShowMenu = false;
    int radius = 180;
    double angle;

    public ArcMenu(Activity context, int[] imageRes) {
        angle = Math.PI / 2 / (imageRes.length - 2);
        radius = Tool.dip2px(context, radius);
        this.context = context;
        this.imageRes = imageRes;
        for (int imagRe : imageRes) {
            ImageView imageView = (ImageView) context.findViewById(imagRe);
            imageViewList.add(imageView);
        }
    }
}

菜单弹出动画,使用ObjectAnimator,对每一个图标进行平移操作,第0个图标为菜单开关,加入旋转动画。

private void openMenu() {
        isShowMenu = true;
        setItemVisible(true);
        ObjectAnimator animator1;
        ObjectAnimator animator2;
        List<ObjectAnimator> objectAnimators = new ArrayList<>();
        AnimatorSet set = new AnimatorSet();

        for (int i = 1; i < imageRes.length; i++) {
            animator1 = ObjectAnimator.ofFloat(imageViewList.get(i), 
"translationX", (float) (-radius * Math.sin(angle * (i-1))));
            animator2 = ObjectAnimator.ofFloat(imageViewList.get(i), "translationY", 
(float) (-radius * Math.cos(angle * (i-1))));
            objectAnimators.add(animator1);
            objectAnimators.add(animator2);
        }

        for (int i = 0; i < objectAnimators.size(); i++) {
            set.playTogether(objectAnimators.get(i));
        }
        set.setDuration(200);
        set.start();
        //第0个图标,菜单图标,加入动画
        ObjectAnimator.ofFloat(imageViewList.get(0),"rotation",0,135f).setDuration(200).start();
    }

同理,关闭菜单

private void closeMenu() {
        isShowMenu = false;
        ObjectAnimator animator1 = null;
        ObjectAnimator animator2;
        List<ObjectAnimator> objectAnimators = new ArrayList<>();
        AnimatorSet set = new AnimatorSet();

        for (int i = 1; i < imageRes.length; i++) {
            animator1 = ObjectAnimator.ofFloat(imageViewList.get(i), "translationX",  0);
            animator2 = ObjectAnimator.ofFloat(imageViewList.get(i), "translationY",  0);
            objectAnimators.add(animator1);
            objectAnimators.add(animator2);
        }

        animator1.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                setItemVisible(false);
                super.onAnimationEnd(animation);
            }
        });

        for (int i = 0; i < objectAnimators.size(); i++) {
            set.playTogether(objectAnimators.get(i));
        }

        set.setDuration(200);
        set.start();
        ObjectAnimator.ofFloat(imageViewList.get(0),"rotation",135f,0).setDuration(200).start();
    }

切换菜单开关

public void switchMenu() {
        if (isShowMenu) {
            closeMenu();
        } else {
            openMenu();
        }
    }

图标被点击后,将所有子图标隐藏,同时调用closMenu(),将图片移回原处。

public void clickItem() {
        setItemVisible(false);
        closeMenu();
    }

    private void setItemVisible(boolean isVisible) {
        for (int i = 1; i < imageRes.length; i++)  {
            if (isVisible) {
                imageViewList.get(i).setVisibility(View.VISIBLE);
            } else {
                imageViewList.get(i).setVisibility(View.GONE);
            }
        }
    }

总结

至此,整个功能大致完成。
现有的功能默认arcmenu为界面右下角,其他位置相应地修改公式即可以实现,可自行进行拓展,兼容不同位置。

这是我们学习基础动画时最常用的几个类

总结

至此,整个功能大致完成。现有的功能默认arcmenu为界面右下角,其他位置相应地修改公式即可以实现,可自行进行拓展,兼容不同位置。

澳门新葡萄京官网首页 3常用类.png

我在这里将根据一个小的平移动画(TranslateAnimation)的demo给大家展示我们需要学习的几个类的实现方法。

首先写一个简单的Demo图

澳门新葡萄京官网首页 4xml.jpg

在这里我写了一个imageview和一个button,分别实现了其onClick属性

 public void move(View view) { TranslateAnimation ta = new TranslateAnimation(0, 200,0,0); ta.setDuration;//持续时间 ta.setFillAfter;//平移完后true代表保存平移后的状态 imageView.startAnimation;//记得需要findViewById } public void click(View view) { Log.i(TAG, "click: 点击了imageview"); }

四个构造参数分别为

  • x轴初始坐标
  • x轴的移动坐标
  • y轴初始坐标
  • y轴的移动坐标因为我们只是简单的平移,所以只写在x轴上平移即可。在click方法中,实现了一个点击安卓机器人图标打印日志的方法,这个需要留意一下,为了铺垫我们为什么要使用后来的ObjectAnimator。

我们来运行一下程序

澳门新葡萄京官网首页 5模拟器.jpg

我们发现,运行出来的结果与我们想的一致,这时候我们点击imageview现在所在的位置,发现出现的问题,Log并没有打印。这是为什么呢

原因是这个方法实现动画的原理在于动画只是在onDraw方法中重新绘制,动画的移动只是视图的单纯移动,所以在平移完后,我们点击图标,相当于点击了个一个空白的布局,如果我们再点击一开始图标初始的位置,我们发现,Log打印了…

你看,这就是个问题,我们该如何解决呢

谷歌官方给我们提供了几个优化好的类

ObjectAnimator

 public void move(View view) { ObjectAnimator.ofFloat(imageView, "translationX", 0f, 200f) .setDuration.start();//1 操纵的对象 2 所需要操纵的对象属性 3 动画变化的范围 } public void click(View view) { Log.i(TAG, "click: 点击了imageview"); }

创建一个ofFloat的静态方法,三个参数分别为

  1. 绑定好的imageview
  2. 一个平移x轴的字符串(所要操作的对象的属性)
  3. 第三个参数为动画变化的范围,这是一个可变的字符串。将这三个参数定义好,再设置一下持续时间以及start()方法,我们就实现了一开始TranslateAnimation类的几行代码了。现在我们运行程序就可以实现同样的效果了。

同时,我们运行项目再次点击平移后机器人图标,发现Log日志可以打印了

那么ObjectAnimator能够实现AnimationSet的动画集合的方法吗,当然可以了

 public void move(View view) { ObjectAnimator.ofFloat(imageView, "translationX", 0f, 200f).setDuration.start();//1 操纵的对象 2 所需要操纵的对象属性 3 动画变化的范围 ObjectAnimator.ofFloat(imageView, "rotation", 0f, 360f).setDuration.start();//异步过程 ObjectAnimator.ofFloat(imageView, "translationY", 0f, 200f).setDuration.start(); } public void click(View view) { Log.i(TAG, "click: 点击了imageview"); }

我定义了同时从y轴,x轴方向偏移,再加上一个旋转360度的动画,我们来运行一下试一试。

澳门新葡萄京官网首页 6ObjectAnimator.jpg

我们发现,动画在一秒内完成三个动作,同样实现了AnimationSet的效果。而且,我们运行项目再次点击平移后机器人图标,发现Log日志可以打印了。

因为此类需要不停的在onDraw方法进行绘制,特别浪费手机的GPU资源,我们该如何优化了,这时候,另一个类的出现就在某些方面替代了ObjectAnimator

PropertyValuesHolderPropertyValuesHolder在优化方面优于ObjectAnimator,至于原理,我也不知道,现在我们来讲一下同样的效果,PropertyValuesHolder该如何实现

public void move(View view) { //优化 PropertyValuesHolder p1 = PropertyValuesHolder.ofFloat("rotation", 0f, 360f); PropertyValuesHolder p2 = PropertyValuesHolder.ofFloat("translationX", 0f, 200f); PropertyValuesHolder p3 = PropertyValuesHolder.ofFloat("translationY", 0f, 200f); ObjectAnimator.ofPropertyValuesHolder(imageView, p1, p2, p3).setDuration.start(); } public void click(View view) { Log.i(TAG, "click: 点击了imageview"); }

通过调用PropertyValuesHolder的静态方法ofFloat,参数与ObjectAnimator.ofFloat参数一致。最后我们通过ObjectAnimator.ofPropertyValuesHolder方法,将我们绑定好的imageview与定义的三个propertyValuesHolder对象传入。通过方法链的形式启动动画最后运行一下项目,发现效果与ObjectAnimator定义时效果一致。

这时候细心的同学应该会发现,AnimationSet是可以定义动画顺序的,那么现在该如何实现呢,别急,还有我们说的最后一个类
AnimatorSet

AnimatorSet

public void move(View view) { //更加丰富的控制效果 ObjectAnimator animator1 = ObjectAnimator.ofFloat(imageView, "translationX", 0f, 200f); ObjectAnimator animator2 = ObjectAnimator.ofFloat(imageView, "translationY", 0f, 200f); ObjectAnimator animator3 = ObjectAnimator.ofFloat(imageView, "rotation", 0f, 360f); AnimatorSet set = new AnimatorSet(); set.playTogether(animator1, animator2, animator3); //set.playSequentially(animator1, animator2, animator3); //set.play(animator1).with(animator2); //set.play(animator2).after(animator3); set.setDuration; set.start(); } public void click(View view) { Log.i(TAG, "click: 点击了imageview"); }

我们用ObjectAnimator类创建了三个对象调用Animator的playTogether方法,将三个对象传入最后start,我们发现也实现了上两个类的效果。我们如何定义顺序呢,我们可以调用set.playSequentially方法,传入,运行项目我们发现顺序就是按照我们传入的三个参数的动画效果执行的。set.play方法以及with,before(),又为我们实现了a动画和b动画同时运行完之后接着再运行c动画的需求,我们就可以更加细节化的处理动画了。

在下一节,我们将实现动画的监听事件源代码github源代码

发表评论

电子邮件地址不会被公开。 必填项已用*标注