admin管理员组

文章数量:1551408

概述

和腾讯管家类似,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设置了帧动画。后面要做的就是触摸事件的处理和动画效果了。

效果图

代码

  1. 清单文件:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android/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>
  1. 布局文件:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android/apk/res/android"
    xmlns:tools="http://schemas.android/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>
  1. 工具类,用于获取屏幕的 大小,做屏幕适配的时候用。
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;
        }

    }


}
  1. 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();
    }

}

注意事项

  1. MainActivity最好直接继承自Activity,android studio2.2新建的Activity默认是AppCompatActivity,AppCompatActivity和上面设置的主题是矛盾的,会发生错误。
  2. 触屏移动有多种方法,这里使用了View(火箭)的setX()和setY()方法,火箭的发射使用属性动画来改变translationY属性实现的,进行View(火箭)位置判断的时候,也是使用了View的X,Y属性进行判断。如果触屏移动使用layout(…)方法实现,那么位置判断,发射过程都需要使用layout(…)方法或者和layout(…)方法相关的属性来实现。保证每次操作都是对View的同一个属性进行操作,否则很可能就达不到预期的效果。

本文标签: 腾讯管家案例火箭发射手机