概述
和腾讯管家类似,360手机卫士上也有类似的功能:拖动小球到屏幕底部,然后小球变成火箭,松手后火箭发射。虽然两者的UI效果各有千秋,但原理基本上是相同的。因为时间的关系,我只实现了部分的UI效果,毕竟快国庆了嘛<( ̄︶ ̄)>,火箭发射后的烟雾效果没有做,也没有做进一步的优化。后面的其实也比较简单,无法是各种动画的使用,有兴趣的可以再改进改进。
原理
腾讯管家的小火箭在屏幕上发射过程中,屏幕上的任何应用都无法响应。其实在发射火箭的过程中,腾讯管家打开了一个Activity,只不过这个Activity是透明的,我们甚至不能感觉到已经打开了一个新的Activity。
所以我们首先要做的,就是要给Activity的Theme设置成透明的。
<activity android:name=".MainActivity"
android:theme="@android:style/Theme.Translucent.NoTitleBar">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
注意MainActivity的Theme设置成了android:style/Theme.Translucent.NoTitleBar。
做好了这一步,我们发现启动该Activity后,该Activity的内容是浮在手机桌面上的,桌面上的其他应用都失去了响应。说明该Activity确实已经打开了,按back键即可退出该Activity。
至于火箭和火光的实现,都是给相应的ImageView设置了帧动画。后面要做的就是触摸事件的处理和动画效果了。
效果图
代码
- 清单文件:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.rocket">
<application android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity"
android:theme="@android:style/Theme.Translucent.NoTitleBar">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
- 布局文件:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.example.rocket.MainActivity">
<ImageView
android:id="@+id/iv_rocket"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
/>
<ImageView
android:id="@+id/box"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:scaleType="fitXY"
android:layout_alignParentBottom="true"
android:src="@drawable/je"/>
<ImageView
android:id="@+id/fire"
android:visibility="invisible"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
android:src="@drawable/g0"/>
</RelativeLayout>
- 工具类,用于获取屏幕的 大小,做屏幕适配的时候用。
public class DensityUtitl {
public static final int WIDTH=0;
public static final int HEIGHT=1;
/**
* 根据手机的分辨率从 dp 的单位 转成为 px(像素)
*/
public int dip2px(Context context, float dpValue) {
final float scale = context.getResources().getDisplayMetrics().density;
return (int) (dpValue * scale + 0.5f);
}
/**
* 根据手机的分辨率从 px(像素) 的单位 转成为 dp
*/
public int px2dip(Context context, float pxValue) {
final float scale = context.getResources().getDisplayMetrics().density;
return (int) (pxValue / scale + 0.5f);
}
/**
* 通过传入的参数类型得到屏幕的宽度或者高度
* @param context
* @param type
* @return
*/
public int getScreenParams(Context context,int type){
WindowManager wm=(WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
DisplayMetrics outMetrics = new DisplayMetrics();
wm.getDefaultDisplay().getMetrics(outMetrics);
int width=outMetrics.widthPixels;
int height=outMetrics.heightPixels;
if(type==DensityUtitl.HEIGHT){
return height;
}else if(type==DensityUtitl.WIDTH){
return width;
}else{
return 0;
}
}
}
- Activity,代码里面有注释。
public class MainActivity extends Activity {
private ImageView ivRocket,box,fire;
private AnimationDrawable animationDrawable;
private int screenWidth,screenHeight;
private RelativeLayout relativeLayout;
private int rocketWidth,rocketHeight;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
screenWidth=new DensityUtitl().getScreenParams(this,DensityUtitl.WIDTH);
screenHeight=new DensityUtitl().getScreenParams(this,DensityUtitl.HEIGHT);
initView();
}
void initView(){
relativeLayout=(RelativeLayout)findViewById(R.id.activity_main);
ivRocket=(ImageView)findViewById(R.id.iv_rocket);
box=(ImageView)findViewById(R.id.box);
fire=(ImageView)findViewById(R.id.fire);
/**
* 火箭和火光都是ImageView,都使用了帧动画。
*/
ivRocket.setImageResource(R.drawable.rocket);
animationDrawable = (AnimationDrawable) ivRocket.getDrawable();
animationDrawable.start();
fire.setImageResource(R.drawable.fire);
animationDrawable = (AnimationDrawable) fire.getDrawable();
animationDrawable.start();
/**
* 火箭的触摸事件
*/
ivRocket.setOnTouchListener(new View.OnTouchListener() {
int startX=0;
int startY=0;
@Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
startX=(int)event.getRawX();
startY=(int)event.getRawY();
displayBox();
break;
case MotionEvent.ACTION_MOVE:
int newX=(int)event.getRawX();
int newY=(int)event.getRawY();
int dx=newX-startX;
int dy=newY-startY;
/**
* 使用View的setX()和setY()方法实现触屏移动。
*/
ivRocket.setX(ivRocket.getX()+dx);
ivRocket.setY(ivRocket.getY()+dy);
startX=newX;
startY=newY;
break;
case MotionEvent.ACTION_UP:
/**
* 定义一个矩形区域,以火光为基础进行定义
*/
int newl=fire.getLeft();
int newt=fire.getTop()-200;
int newr=fire.getRight();
int tranX=(int)ivRocket.getX();
int tranY=(int)ivRocket.getY();
int offsetLeft=(screenWidth-rocketWidth)/2;
int offsetRight=(screenWidth+rocketWidth)/2;
/**
* 当将火箭拖动到屏幕底部中间的火光位置时,显示火光,并且执行发射火箭的方法
*/
if(offsetLeft>newl&&offsetRight<newr&&tranY>newt){
showFire();
launch();
}
hideBox();
break;
}
return true;
}
});
/**
* 控件的宽高不能直接获取,需要在线程中获取。
*/
relativeLayout.post(new Runnable() {
@Override
public void run() {
rocketWidth=ivRocket.getWidth();
rocketHeight=ivRocket.getHeight();
int boxHeight=box.getHeight();
box.setTranslationY(boxHeight);
/**
* 将火光垂直上移至发射口,乘以0.31135f是通过图片的宽高比例算出来的
*/
fire.setTranslationY(-boxHeight*0.31135f);
}
});
}
/**
* 显示屏幕底部的盒子
*/
void displayBox(){
ObjectAnimator objectAnimator=ObjectAnimator.ofFloat(box,"translationY",300f,0);
ObjectAnimator objectAnimator2=ObjectAnimator.ofFloat(box,"alpha",0.2f,1.0f);
AnimatorSet set=new AnimatorSet();
set.setDuration(1000);
set.playTogether(objectAnimator,objectAnimator2);
set.start();
}
/**
* 隐藏屏幕底部的盒子
*/
void hideBox(){
ObjectAnimator objectAnimator=ObjectAnimator.ofFloat(box,"translationY",0,300f);
ObjectAnimator objectAnimator2=ObjectAnimator.ofFloat(box,"alpha",1.0f,0.0f);
AnimatorSet set=new AnimatorSet();
set.setDuration(1000);
set.playTogether(objectAnimator,objectAnimator2);
set.start();
}
/**
* 显示火光
*/
void showFire(){
fire.setVisibility(View.VISIBLE);
ObjectAnimator objectAnimator=ObjectAnimator.ofFloat(box,"alpha",0.2f,1.0f);
objectAnimator.setDuration(600);
objectAnimator.start();
}
/**
* 发射火箭
*/
void launch(){
ObjectAnimator objectAnimator=ObjectAnimator.ofFloat(ivRocket,"translationY",ivRocket.getTranslationY()-screenHeight+240);
objectAnimator.setDuration(1500);
objectAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
/**
* 发射结束后,隐藏火光
*/
fire.setVisibility(View.INVISIBLE);
}
});
objectAnimator.start();
}
}
注意事项
- MainActivity最好直接继承自Activity,android studio2.2新建的Activity默认是AppCompatActivity,AppCompatActivity和上面设置的主题是矛盾的,会发生错误。
- 触屏移动有多种方法,这里使用了View(火箭)的setX()和setY()方法,火箭的发射使用属性动画来改变translationY属性实现的,进行View(火箭)位置判断的时候,也是使用了View的X,Y属性进行判断。如果触屏移动使用layout(…)方法实现,那么位置判断,发射过程都需要使用layout(…)方法或者和layout(…)方法相关的属性来实现。保证每次操作都是对View的同一个属性进行操作,否则很可能就达不到预期的效果。