今天给大家带来动画的实战练习,今天是一年一度的高考,祝各位考生能梦寐以求.
完成效果
今天给大家带来2个效果:
- 一个是ListView的滑动进入
- 另一个是右下角的点击弹框效果
ListView滑动进入动画
第一步,先在布局中使用LIstView,在res/layout_animation创建文件,并使用android:layoutAnimation="@anim/layout_animation"引用到ListView中
第二步:layout_animation文件根目录为layoutAnimation
<?xml version="1.0" encoding="utf-8"?>
<layoutAnimation xmlns:android="http://schemas.android.com/apk/res/android"
android:delay="0.3"
android:animationOrder="normal"
android:animation="@anim/set_buttonanimator_activity"
>
</layoutAnimation>
- android:delay= “float” 可以理解为每个个item相隔滑动的时间
- android:animationOrder= “normal | random | reverse”
- normal 按顺序弹出
- random 随机弹出
- reverse 倒序弹出
- android:animation = “@anim/XXX” 弹出的动画
第三步: 在anim下创建set_buttonanimator_activity.xml设置向右滑动,并且透明度由0-1,时长为1s
<set xmlns:android="http://schemas.android.com/apk/res/android" android:duration="1000" >
<translate android:fromXDelta="-50%p" android:toXDelta="0"/>
<alpha android:fromAlpha="0.0" android:toAlpha="1.0"/>
</set>
第四步:在布局中使用ListView
ListView listView = findViewById(R.id.listview);
ArrayList<String> list = new ArrayList<>();
list.add("测试数据1");
list.add("测试数据2");
list.add("测试数据3");
list.add("测试数据4");
list.add("测试数据5");
list.add("测试数据6");
这里使用的布局是android中自带的
ArrayAdapter<String> stringArrayAdapter = new ArrayAdapter<>(this, android.R.layout.simple_expandable_list_item_1, list);
listView.setAdapter(stringArrayAdapter);
<layoutAnimation xmlns:android="http://schemas.android.com/apk/res/android"
android:delay="0.3"
android:animationOrder="normal"
android:animation="@anim/set_buttonanimator_activity"
>
</layoutAnimation>
android:animationOrder = “normal” 正序播放效果:
android:animationOrder=“reverse” 倒序播放效果
android:animationOrder=“random” 随机效果
动态代码实现
//代码设置通过加载XML动画设置文件来创建一个Animation对象;
Animation animation= AnimationUtils.loadAnimation(this,R.anim.set_buttonanimator_activity); //得到一个LayoutAnimationController对象;
LayoutAnimationController controller = new LayoutAnimationController(animation); //设置控件显示的顺序;
controller.setOrder(LayoutAnimationController.ORDER_RANDOM); //设置控件显示间隔时间;
controller.setDelay(0.3f); //为ListView设置LayoutAnimationController属性;
listView.setLayoutAnimation(controller);
listView.startLayoutAnimation();
- ORDER_NORMAL = 0 正常顺序
- ORDER_REVERSE = 1倒序
- ORDER_RANDOM = 2 随机顺序
大家可以从源码中找到注释来看是什么意思;
菜单弹框效果:
滑动的效果很简单,alpha从0 - 1 , scale 从0 - 1,最复杂的就是找到每个控件对应的X,和Y的位置,接下来给大家简单分析一下.
从这张图中可以简单地看出,想要求得X和Y的距离,就必须获得半径r,和夹角a,半径默认为600,那么每个a的夹角是多少呢?从黄色控件和,紫色控件来看,他们的夹角为直角,就是90°,那么他们吧5个控件分为了4等份,那么每一份的夹角就是90 / 4 = 22.5°,那么蓝色与紫色的夹角就是22.5 * 2 = 45°,红色与紫色就是 22.5 * 3 = 67.5黄色与紫色就是22.5 * 4 = 90°.然后通过数学公式:
X = r * sin(a)
Y = r * cos(a)
即可获得X和Y的坐标
想必大家知道Java提供了一个Math类:
/**
* Returns the trigonometric sine of an angle. Special cases:
* <ul><li>If the argument is NaN or an infinity, then the
* result is NaN.
* <li>If the argument is zero, then the result is a zero with the
* same sign as the argument.</ul>
*
* <p>The computed result must be within 1 ulp of the exact result.
* Results must be semi-monotonic.
*
* @param a an angle, in radians.
* @return the sine of the argument.
*/
求正弦值
@CriticalNative
public static native double sin(double a);
/**
* Returns the trigonometric cosine of an angle. Special cases:
* <ul><li>If the argument is NaN or an infinity, then the
* result is NaN.</ul>
*
* <p>The computed result must be within 1 ulp of the exact result.
* Results must be semi-monotonic.
*
* @param a an angle, in radians.
* @return the cosine of the argument.
*/
求余弦值
@CriticalNative
public static native double cos(double a);
/**
* Returns the trigonometric tangent of an angle. Special cases:
* <ul><li>If the argument is NaN or an infinity, then the result
* is NaN.
* <li>If the argument is zero, then the result is a zero with the
* same sign as the argument.</ul>
*
* <p>The computed result must be within 1 ulp of the exact result.
* Results must be semi-monotonic.
*
* @param a an angle, in radians.
* @return the tangent of the argument.
*/
求正切值
@CriticalNative
public static native double tan(double a);
注意:这里的参数不是填的角度,而是填的角度对应的弧度
接下来看看什么是弧度:
简单的来说,夹角a对面的就是a的弧度;
使用Math类中的toRadians(flaot)方法,参数填的是角度
/**
* Converts an angle measured in degrees to an approximately
* equivalent angle measured in radians. The conversion from
* degrees to radians is generally inexact.
*
* @param angdeg an angle, in degrees
* @return the measurement of the angle {@code angdeg}
* in radians.
* @since 1.2
*/
public static double toRadians(double angdeg) {
return angdeg / 180.0 * PI;
}
这样就可以初略的得出:
X = r * sin (Math.toRadians(22))
Y = r * cos (Math.toRadians(22))
接下来直接上代码了:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ButtonAnimatorActivity">
<Button
android:id="@+id/openButton"
style="@style/OpenButton"
android:background="@color/colorPrimary"
android:layout_margin="30dp"
/>
<Button
android:id="@+id/bt1"
style="@style/OpenButton"
android:background="#ff00ff"
android:visibility="gone"
android:layout_margin="30dp"
/>
<Button
android:id="@+id/bt2"
style="@style/OpenButton"
android:background="#00ffff"
android:visibility="gone"
android:layout_margin="30dp"
/>
<Button
android:id="@+id/bt3"
style="@style/OpenButton"
android:background="#0048FF"
android:visibility="gone"
android:layout_margin="30dp"
/>
<Button
android:id="@+id/bt4"
style="@style/OpenButton"
android:background="#FF003B"
android:layout_margin="30dp"
android:visibility="gone"
/>
<Button
android:id="@+id/bt5"
style="@style/OpenButton"
android:visibility="gone"
android:background="#FFDD00"
android:layout_margin="30dp"
/>
</RelativeLayout>
将共同的代码抽取到values/styles下,然后在引用,可以减少写很多重复代码;
<style name="OpenButton" >
<item name="android:layout_width">50dp</item>
<item name="android:layout_height">50dp</item>
<item name="android:layout_alignParentEnd">true</item>
<item name="android:layout_alignParentBottom">true</item>
</style>
接下来获取控件Id,并且设置点击事件:
private Button openButton;
private Button bt1;
private Button bt2;
private Button bt3;
private Button bt4;
private Button bt5;
openButton = findViewById(R.id.openButton);
bt1 = findViewById(R.id.bt1);
bt2 = findViewById(R.id.bt2);
bt3 = findViewById(R.id.bt3);
bt4 = findViewById(R.id.bt4);
bt5 = findViewById(R.id.bt5);
openButton.setOnClickListener(this);
bt1.setOnClickListener(this);
bt2.setOnClickListener(this);
bt3.setOnClickListener(this);
bt4.setOnClickListener(this);
bt5.setOnClickListener(this);
设置此变量是为了设置点击按钮出现不同的效果,因为这里需要打开和关闭
Boolean buttonType = false;
@Override
public void onClick(View v) {
if (v.getId() == R.id.openButton) {
if (!buttonType) {
buttonType = true;
打开bt1 - bt5
setOpenButton(bt1,0,5,600);
setOpenButton(bt2,1,5,600);
setOpenButton(bt3,2,5,600);
setOpenButton(bt4,3,5,600);
setOpenButton(bt5,4,5,600);
}else{
buttonType = false;
关闭bt1 - bt5
setCloseButton(bt1,0,5,600);
setCloseButton(bt2,1,5,600);
setCloseButton(bt3,2,5,600);
setCloseButton(bt4,3,5,600);
setCloseButton(bt5,4,5,600);
}
}else{
Toast.makeText(this, "点击了"+v.getId(), Toast.LENGTH_SHORT).show();
}
}
打开按钮方法:
/**
*
* @param button 控件对象
* @param index 当前按钮下标
* @param number 按钮总个数
* @param radio 圆的半径
*/
private void setOpenButton(Button button, int index, int number, int radio) {
button.setVisibility(View.VISIBLE);
double degree = Math.toRadians(90)/(number - 1) * index;
Log.i("setOpenButton",degree+"");
这里需要注意,获取到结果之后要加负号(-),因为正数是往右下角弹出的,咋们现在需要向左上角弹出.
float x = (float) -(radio * Math.sin(degree));
float y = (float) -(radio * Math.cos(degree));
Log.i("szjdegree"+index,x+"\t\t"+y);
AnimatorSet animatorSet = new AnimatorSet();
animatorSet.playTogether(
ObjectAnimator.ofFloat(button,"translationX",0,x),
ObjectAnimator.ofFloat(button,"translationY",0,y),
ObjectAnimator.ofFloat(button,"alpha",0,1),
ObjectAnimator.ofFloat(button,"scaleX",0,1),
ObjectAnimator.ofFloat(button,"scaleY",0,1)
);
animatorSet.setDuration(500);
animatorSet.start();
}
按钮的关闭方法:
private void setCloseButton(final Button button, int index, int number, int radio) {
double degree = Math.toRadians(90) / (number - 1) * index;
int x = (int) - (radio * Math.sin(degree));
int y = (int) - (radio * Math.cos(degree));
Log.i("setCloseButton",degree+"\n");
Log.i("setXXX"+ index,x+"\t\t"+y);
AnimatorSet animatorSet = new AnimatorSet();
animatorSet.playTogether(
ObjectAnimator.ofFloat(button,"translationX",x , 0),
ObjectAnimator.ofFloat(button,"translationY",y , 0),
ObjectAnimator.ofFloat(button,"alpha",1 , 0),
ObjectAnimator.ofFloat(button,"scaleX",1 , 0),
ObjectAnimator.ofFloat(button,"scaleY",1 , 0)
);
animatorSet.setDuration(500);
animatorSet.start();
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
button.setVisibility(View.INVISIBLE);
}
},500);
}
按钮的打开和关闭都是一样的,只是有一点不同:
- 打开是从0 - X && 0 - Y
- 关闭时从 X - 0 &* Y - 0
要有细心地朋友可能发现在关闭的时候还多了一个方法:
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
button.setVisibility(View.INVISIBLE);
}
},500);
为什么要加这个东西呢?咋们先看看不加这个东西的效果:
为什么会出现这种情况呢?因为在第二次缩放控件的时候,他只是回到了绿色控件的地方,并且透明度(alpha)由1变到了0,只是他的状态不存在了,可是他的值还有,所以他不显示的时候需要吧他隐藏掉,
我这里用到的是handler的延时操作:
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
button.setVisibility(View.INVISIBLE);
}
},500);
因为代码执行的很快很快,所以不用担心有什么问题,如果觉得不保险,也可以对animatorSet监听,当动画结束的时候会响应onAnimationEnd()事件
animatorSet.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {
}
@Override
public void onAnimationEnd(Animator animation) {
button.setVisibility(View.INVISIBLE);
}
@Override
public void onAnimationCancel(Animator animation) {
}
@Override
public void onAnimationRepeat(Animator animation) {
}
});
菜单点击效果参考链接:
Git地址链接: langyangyang.
请各位大佬留下宝贵的建议.