自定义水波球清理内存的悬浮窗小工具

一、概述

现在一些手机管家都会有一个用来清理内存的悬浮窗小工具,感觉挺实用的,就自己做了一个。首先介绍一下这个工具的功能,除了可以清理内存,还有调节手机屏幕亮度、手电筒、无线网、移动数据、蓝牙、GPS开关的功能。先上图,感受一波:

清理手机内存

   

一些常用功能的开关

二、功能实现

1、悬浮窗

   

MainActivity只有两个按钮,控制悬浮窗的打开和关闭。这里我是用Service去控制的。下面我把FloatWindowService的代码贴出来:

public class FloatWindowService extends Service {
    /**
     * 用于在线程中创建或移除悬浮窗。
     */
    private Handler handler = new Handler();
    private FloatManager floatManager;
    private Runnable runnable;

    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        floatManager = new FloatManager(getApplicationContext());
        //每隔1秒会更新一次悬浮窗状态
        runnable = new Runnable() {
            @Override
            public void run() {
                boolean isHome = isHome();
                floatManager.trigger(isHome);
                handler.postDelayed(this, 1000);
            }
        };
        handler.post(runnable);
        return super.onStartCommand(intent, flags, startId);
    }

    /**
     * 判断当前界面是否是桌面
     */
    private boolean isHome() {
        ActivityManager mActivityManager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
        List<RunningTaskInfo> rti = mActivityManager.getRunningTasks(1);
        return getHomes().contains(rti.get(0).topActivity.getPackageName());
    }

    /**
     * 获得属于桌面的应用的应用包名称
     *
     * @return 返回包含所有包名的字符串列表
     */
    private List<String> getHomes() {
        List<String> names = new ArrayList<String>();
        PackageManager packageManager = this.getPackageManager();
        Intent intent = new Intent(Intent.ACTION_MAIN);
        intent.addCategory(Intent.CATEGORY_HOME);
        List<ResolveInfo> resolveInfo = packageManager.queryIntentActivities(intent,
                PackageManager.MATCH_DEFAULT_ONLY);
        for (ResolveInfo ri : resolveInfo) {
            names.add(ri.activityInfo.packageName);
        }
        return names;
    }

    /**
     * 服务销毁时清除任务
     */
    @Override
    public void onDestroy() {
        super.onDestroy();
        handler.removeCallbacks(runnable);
        floatManager.removeWindow(getApplicationContext());
    }
}

该悬浮窗只有在桌面才会出现,当打开其他应用的时候会隐藏。我这里用了Handler去每一秒执行一次判断悬浮窗的状态,式显示还是隐藏。注意:在Service的onDestory()的方法中一定要执行handler.removeCallbacks(),否则不能关闭悬浮窗。

下面说一下悬浮窗的创建,定义一个类继承LinearLayout,重写它的onTouchEvent()方法,实现拖动和点击的效果。核心代码如下:

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        try {
            switch (event.getAction()) {
                case MotionEvent.ACTION_DOWN:
                    // 手指按下时记录必要数据,纵坐标的值都需要减去状态栏高度
                    xInView = event.getX();
                    yInView = event.getY();
                    xDownInScreen = event.getRawX();
                    yDownInScreen = event.getRawY() - getStatusBarHeight();
                    xInScreen = event.getRawX();
                    yInScreen = event.getRawY() - getStatusBarHeight();
                    break;
                case MotionEvent.ACTION_MOVE:
                    xInScreen = event.getRawX();
                    yInScreen = event.getRawY() - getStatusBarHeight();
                    // 手指移动的时候更新悬浮窗的位置
                    updateViewPosition();
                    break;
                case MotionEvent.ACTION_UP:
                    // 如果手指离开屏幕时,xDownInScreen和xInScreen相等,且yDownInScreen和yInScreen相等,则视为触发了单击事件。
                    if (xDownInScreen == xInScreen && yDownInScreen == yInScreen) {
                        Intent intent = new Intent(mContext, CleanActivity.class);
                        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                        mContext.startActivity(intent);
                    }
                    break;
                default:
                    break;
            }
        } catch (Exception e) {

        }
        return true;
    }

这里我创建了一个管理悬浮窗的类,用来获取悬浮窗的显示状态,控制悬浮窗的显示和移除,并在悬浮小球上显示当前内存使用量,同样也用到了Handler类。代码如下:

public class FloatManager {

    private FloatView floatView;

    private LayoutParams floatParams;

    private WindowManager windowManager;

    private Context context;

    private Handler handler = new Handler();

    private boolean isFirst = true;

    private long currentTime;

    public FloatManager(Context context) {
        this.context = context;
    }

    /**
     * 判断悬浮窗状态
     */
    public void trigger(boolean isHome) {
        try {
            if (isHome && !isShowing()) {
                handler.post(createRunnable);
                handler.post(updateRunnable);
                if (isFirst) {
                    handler.post(alertRunnable);
                    currentTime = System.currentTimeMillis();
                    isFirst = false;
                }
            } else if (!isHome && isShowing()) {
                handler.post(destroyRunnable);
            } else if (isHome && isShowing()) {
                handler.removeCallbacks(updateRunnable);
                handler.post(updateRunnable);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private Runnable createRunnable = new Runnable() {
        @Override
        public void run() {
            try {
                createWindow(context);
            } catch (Exception e) {

            }
        }
    };

    private Runnable destroyRunnable = new Runnable() {
        @Override
        public void run() {
            try {
                handler.removeCallbacks(updateRunnable);
                removeWindow(context);
            } catch (Exception e) {

            }
        }
    };

    private Runnable updateRunnable = new Runnable() {
        @Override
        public void run() {
            try {
                updateUsedPercent(context);
            } catch (Exception e) {

            }
        }
    };

    /**
     * 每1秒判断一次,是否满足内存占用大于80%,时间间隔至少30分钟,且在桌面,则弹出吐司提示用户清理
     */
    private Runnable alertRunnable = new Runnable() {
        @Override
        public void run() {
            try {
                long tmp = System.currentTimeMillis();
                long time = tmp - currentTime;
                if (MemoryManager.getUsedPercentValue(context) >= 80 && time >= 1000 * 60 * 30 && isShowing()) {
                    Toast.makeText(context, "Mobile phone need to clean up!", Toast.LENGTH_LONG).show();
                    currentTime = tmp;
                }
                handler.postDelayed(alertRunnable, 1000);
            } catch (Exception e) {

            }
        }
    };

    /**
     * 创建一个悬浮窗。初始位置为屏幕的右部中间位置
     */
    public void createWindow(Context context) {
        try {
            WindowManager windowManager = getWindowManager(context);
            int screenWidth = windowManager.getDefaultDisplay().getWidth();
            int screenHeight = windowManager.getDefaultDisplay().getHeight();
            if (floatView == null) {
                floatView = new FloatView(context);
                if (floatParams == null) {
                    floatParams = new LayoutParams();
                    floatParams.type = LayoutParams.TYPE_PHONE;
                    floatParams.format = PixelFormat.RGBA_8888;
                    floatParams.flags = LayoutParams.FLAG_NOT_TOUCH_MODAL
                            | LayoutParams.FLAG_NOT_FOCUSABLE;
                    floatParams.gravity = Gravity.LEFT | Gravity.TOP;
                    floatParams.width = FloatView.viewWidth;
                    floatParams.height = FloatView.viewHeight;
                    floatParams.x = screenWidth;
                    floatParams.y = screenHeight / 2;
                }
                floatView.setParams(floatParams);
                windowManager.addView(floatView, floatParams);
            }
        } catch (Exception e) {

        }
    }

    /**
     * 将悬浮窗从屏幕上移除
     */
    public void removeWindow(Context context) {
        try {
            if (floatView != null) {
                WindowManager windowManager = getWindowManager(context);
                windowManager.removeView(floatView);
                floatView = null;
            }
        } catch (Exception e) {

        }
    }

    /**
     * 更新悬浮窗的TextView上的数据,显示内存使用的百分比。
     */
    public void updateUsedPercent(Context context) {
        try {
            if (floatView != null) {
                TextView percentView = (TextView) floatView.findViewById(R.id.float_percent);
                percentView.setText(MemoryManager.getUsedPercentValue(context) + "%");
            }
        } catch (Exception e) {

        }
    }

    /**
     * 是否有悬浮窗显示在屏幕上。
     */
    public boolean isShowing() {
        return floatView != null;
    }

    private WindowManager getWindowManager(Context context) {
        try {
            if (windowManager == null) {
                windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
            }
        } catch (Exception e) {

        }
        return windowManager;
    }

}

2、水波球

这是一个自定义View,由三个函数完成:measure()、layout()、draw(),其内部又分别包含了onMeasure()、onLayout()、onDraw()三个子方法。measure操作主要用于计算视图的大小,即视图的宽度和长度。layout操作用于设置视图在屏幕中显示的位置。draw操作利用前两部得到的参数,将视图显示在屏幕上。想实现标准正余弦水波纹,可以用具体函数模拟出具体的轨迹。核心代码如下:

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        try {
            canvas.drawPath(blowWavePath, blowWavePaint);
            canvas.drawPath(aboveWavePath, aboveWavePaint);

            Paint mPaint = new Paint();
            mPaint.setColor(Color.rgb(64, 64, 64));
            mPaint.setAntiAlias(true);// 抗锯齿
            // 绘制空心圆矩形
            canvas.save();
            Path path = new Path();
            path.reset();
            path.setFillType(Path.FillType.EVEN_ODD);
            mPaint.setStyle(Paint.Style.FILL);
            RectF rectF = new RectF();
            rectF.set(0, 0, getWidth(), getHeight());
            path.addOval(rectF, Path.Direction.CCW);
            rectF.set(0, 0, getHeight(), getBottom());
            path.addRoundRect(rectF, 0, 0, Path.Direction.CW);
            canvas.drawPath(path, mPaint);
            canvas.restore();

            // 定义画笔2
            Paint paint2 = new Paint();
            // 消除锯齿
            paint2.setAntiAlias(true);
            // 设置画笔的颜色
            paint2.setColor(Color.GRAY);
            paint2.setStrokeWidth(mStokeWidth);

            paint2.setStyle(Paint.Style.STROKE);
            // 画一个空心圆
            canvas.drawCircle((float) ((getWidth() >> 1)), (float) (getHeight() >> 1),
                    (float) ((getWidth() >> 1) - (paint2.getStrokeWidth()) / 2), paint2);
        } catch (Exception e) {

        }
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        try {
            setMeasuredDimension(measure(widthMeasureSpec, true), measure(heightMeasureSpec, false));
        } catch (Exception e) {

        }
    }

    private int measure(int measureSpec, boolean isWidth) {
        int result = 0;
        try {
            int mode = MeasureSpec.getMode(measureSpec);
            int size = MeasureSpec.getSize(measureSpec);
            int padding = isWidth ? getPaddingLeft() + getPaddingRight() : getPaddingTop() + getPaddingBottom();
            if (mode == MeasureSpec.EXACTLY) {
                result = size;
            } else {
                result = isWidth ? getSuggestedMinimumWidth() : getSuggestedMinimumHeight();
                result += padding;
                if (mode == MeasureSpec.AT_MOST) {
                    if (isWidth) {
                        result = Math.max(result, size);
                    } else {
                        result = Math.min(result, size);
                    }
                }
            }
        } catch (Exception e) {

        }
        return result;
    }

    /**
     * 计算波动轨迹
     */
    private void calculatePath() {
        try {
            aboveWavePath.reset();
            blowWavePath.reset();

            getWaveOffset();

            aboveWavePath.moveTo(0, getHeight());
            for (float i = 0; x_zoom * i <= getRight() + max_right; i += offset) {
                aboveWavePath.lineTo((x_zoom * i), (float) (y_zoom * Math.cos(i + aboveOffset)) + waveToTop);
            }
            aboveWavePath.lineTo(getRight(), getHeight());

            blowWavePath.moveTo(0, getHeight());
            for (float i = 0; x_zoom * i <= getRight() + max_right; i += offset) {
                blowWavePath.lineTo((x_zoom * i), (float) (y_zoom * Math.cos(i + blowOffset)) + waveToTop);
            }
            blowWavePath.lineTo(getRight(), getHeight());
        } catch (Exception e) {

        }
    }

效果如下:

   

这是清理内存之前和之后的截图,该Activity上面一半是透明的布局。这里我创建了两层水波,更加生动,设置了当内存使用量>=70%的时候,颜色为红色,<70%的时候为蓝色。这里我把获取内存和清理内存的方法贴出来:

    /**
     * 获取当前可用内存
     */
    private static long getAvailableMemory(Context context) {
        long ret = 0L;
        try {
            ActivityManager.MemoryInfo memoryInfo = new ActivityManager.MemoryInfo();
            ActivityManager manager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
            manager.getMemoryInfo(memoryInfo);
            ret = memoryInfo.availMem;
        } catch (Exception e) {

        }
        return ret;
    }

    /**
     * 获取总共内存
     */
    public static long getTotalMemory() {
        long totalMemorySize = 0L;
        try {
            String dir = "/proc/meminfo";
            FileReader fr = new FileReader(dir);
            BufferedReader br = new BufferedReader(fr, 2048);
            String memoryLine = br.readLine();
            String subMemoryLine = memoryLine.substring(memoryLine.indexOf("MemTotal:"));
            br.close();
            totalMemorySize = Long.parseLong(subMemoryLine.replaceAll("\\D+", "")) * 1024;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return totalMemorySize;
    }

    /**
     * 获取正在运行的进程数
     */
    public static int getRunningProcess(Context context) {
        int ret = 0;
        try {
            ActivityManager manager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
            ret = manager.getRunningAppProcesses().size();
        } catch (Exception e) {

        }
        return ret;
    }

    /**
     * 清理内存,返回释放的内存
     */
    public static long clearMemory(Context context) {
        long beforeMem = 0L;
        long afterMem = 0L;
        try {
            ActivityManager manager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
            List<ActivityManager.RunningAppProcessInfo> list = manager.getRunningAppProcesses();
            //清理之前的可用内存
            beforeMem = getAvailableMemory(context);

            if (list != null) {
                for (ActivityManager.RunningAppProcessInfo info : list) {
                    if (info.importance > ActivityManager.RunningAppProcessInfo.IMPORTANCE_SERVICE) {
                        for (String pkg : info.pkgList) {
                            Log.d(TAG, "kill package: " + pkg);
                            manager.killBackgroundProcesses(pkg);
                        }
                    }
                }
            }
            //清理之后的可用内存
            afterMem = getAvailableMemory(context);
        } catch (Exception e) {

        }
        return afterMem - beforeMem;
    }

清理内存的时候,小球的水面是先下降到0,然后再上升到当前内存使用量的位置,变化的速度可以自己设置,代码我就不贴了,文末会把完整的源码给大家。

3、功能开关

这个部分兼容性比较麻烦,因为不同的手机设置可能不太一样,我这里用的测试机是红米Note,像这类定制的系统权限比较多,所以GPS的开关我是直接调用系统的设置界面,我尽量适配大部分机型。

首先,实现这些开关都需要权限,我先把AndroidMainfest.xml中的权限列出来:

    <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
    <uses-permission android:name="android.permission.INTERNET" />
    <!--移动数据流量-->
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
    <uses-permission android:name="android.permission.WRITE_APN_SETTINGS" />
    <!--亮度-->
    <uses-permission android:name="android.permission.WRITE_SETTINGS" />
    <uses-permission android:name="android.permission.DEVICE_POWER" />
    <!--GPS-->
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
    <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
    <!--wifi-->
    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
    <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
    <!--蓝牙-->
    <uses-permission android:name="android.permission.BLUETOOTH" />
    <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
    <!--闪光灯-->
    <uses-permission android:name="android.permission.CAMERA" />
    <!--清理后台程序-->
    <uses-permission android:name="android.permission.KILL_BACKGROUND_PROCESSES" />
    <uses-permission android:name="android.permission.GET_TASKS" />

(1)手电筒

这里我把手电筒状态的保存和设置手电筒状态的方法写在了MyApplication中,因为如果打开了手电筒,当退出这个页面的时候,手电筒应该还是亮着的,所以就必须保存在Application中。代码如下:

    /**
     * 判断手电筒是否开启
     */
    public static boolean isLightOpen() {
        boolean isLightOpen = false;
        try {
            if (camera == null) {
                camera = Camera.open();
            }
            params = camera.getParameters();
            isLightOpen = params.getFlashMode().equals(Camera.Parameters.FLASH_MODE_TORCH) ? true : false;
        } catch (Exception e) {
        }
        return isLightOpen;
    }

    /**
     * 打开手电筒
     */
    public static void openLight(Context context) {
        try {
            PackageManager pm = context.getPackageManager();
            FeatureInfo[] features = pm.getSystemAvailableFeatures();
            for (FeatureInfo f : features) {
                if (PackageManager.FEATURE_CAMERA_FLASH.equals(f.name))   //判断设备是否支持闪光灯
                {
                    params.setFlashMode(Camera.Parameters.FLASH_MODE_TORCH);
                    camera.setParameters(params);
                    camera.startPreview(); // 开始亮灯
                }
            }
        } catch (Exception e) {
        }
    }

    /**
     * 关闭手电筒
     */
    public static void closeLight() {
        try {
            if (camera != null) {
                params.setFlashMode(Camera.Parameters.FLASH_MODE_OFF);
                camera.setParameters(params);
                camera.stopPreview(); // 关掉亮灯
                camera.release(); // 关掉照相机
                camera = null;
            }
        } catch (Exception e) {
        }
    }

(2)WIFI

Wifi开关由WifiManager这个类控制实现。这里我注册了一个广播接收者,监听WIFI的状态变化,当Wifi开关改变时,系统会向外界发送广播android.net.wifi.WIFI_STATE_CHANGED;核心代码如下:

//注册监听wifi状态的广播接收者
      wifiReceiver = new WifiReceiver();
      IntentFilter wififilter = new IntentFilter();
      wififilter.setPriority(2147483647);
      wififilter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION);
      registerReceiver(wifiReceiver, wififilter);
      bt_wifi.setOnClickListener(new View.OnClickListener() {
          @Override
          public void onClick(View v) {
              try {
                  if (mWifiManager.isWifiEnabled()) {
                      mWifiManager.setWifiEnabled(false);
                  } else {
                      mWifiManager.setWifiEnabled(true);
                  }
              } catch (Exception e) {
              }
          }
      });

(3)数据流量

移动数据流量由ConnectivityManager类控制实现,这个类实现设置和获取移动流量状态的方法是隐藏的,所以我们只能通过反射来实现。点击这个开关的时候我会先判断当前的数据流量是否可用,即有没有SIM卡,这里我用的测试机没有安装SIM卡,所以点击的时候弹了一个吐司“数据流量不可用”,这也体现了代码的严谨性和健壮性。核心代码如下:

    /**
     * 设置数据网络开关
     */
    public boolean changeNetConnection(Context context, boolean on) {
        try {
            if (Build.VERSION.SDK_INT == Build.VERSION_CODES.FROYO) {
                Method dataConnSwitchmethod;

                TelephonyManager telephonyManager = (TelephonyManager) context
                        .getSystemService(Context.TELEPHONY_SERVICE);
                Class<?> telephonyManagerClass = Class.forName(telephonyManager
                        .getClass().getName());
                Method getITelephonyMethod = telephonyManagerClass
                        .getDeclaredMethod("getITelephony");
                getITelephonyMethod.setAccessible(true);
                Object ITelephonyStub = getITelephonyMethod
                        .invoke(telephonyManager);
                Class<?> ITelephonyClass = Class.forName(ITelephonyStub
                        .getClass().getName());

                if (on) {
                    dataConnSwitchmethod = ITelephonyClass
                            .getDeclaredMethod("enableDataConnectivity");
                } else {
                    dataConnSwitchmethod = ITelephonyClass
                            .getDeclaredMethod("disableDataConnectivity");
                }
                dataConnSwitchmethod.setAccessible(true);
                dataConnSwitchmethod.invoke(ITelephonyStub);
            } else {
                final ConnectivityManager conman = (ConnectivityManager) context
                        .getSystemService(Context.CONNECTIVITY_SERVICE);
                final Class<?> conmanClass = Class.forName(conman.getClass()
                        .getName());
                final Field iConnectivityManagerField = conmanClass
                        .getDeclaredField("mService");
                iConnectivityManagerField.setAccessible(true);
                final Object iConnectivityManager = iConnectivityManagerField
                        .get(conman);
                final Class<?> iConnectivityManagerClass = Class
                        .forName(iConnectivityManager.getClass().getName());
                final Method setMobileDataEnabledMethod = iConnectivityManagerClass
                        .getDeclaredMethod("setMobileDataEnabled", Boolean.TYPE);
                setMobileDataEnabledMethod.setAccessible(true);
                setMobileDataEnabledMethod.invoke(iConnectivityManager, on);
            }
            return true;
        } catch (Exception e) {

        }
        return false;
    }

(4)蓝牙

蓝牙开关主要调用BluetoothAdapter相关方法实现,蓝牙有四种状态:正在打开、打开、正在关闭、关闭。蓝牙状态改变,系统向外界发送广播android.bluetooth.adapter.action.STATE_CHANGED或android.bluetooth.a2dp.profile.action.CONNECTION_STATE_CHANGED;核心代码如下:

//蓝牙开关
       mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
       updateBluetooth();
       //注册监听蓝牙状态的广播接收者
       blueToothReceiver = new BlueToothReceiver();
       IntentFilter bluefilter = new IntentFilter();
       bluefilter.setPriority(2147483647);
       bluefilter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
       registerReceiver(blueToothReceiver, bluefilter);
       bt_bluetooth.setOnClickListener(new View.OnClickListener() {
           @Override
           public void onClick(View v) {
               try {
                   switch (getBluetoothStatus()) {
                       case BluetoothAdapter.STATE_ON:
                       case BluetoothAdapter.STATE_TURNING_ON:
                           mBluetoothAdapter.disable();
                           break;
                       case BluetoothAdapter.STATE_OFF:
                       case BluetoothAdapter.STATE_TURNING_OFF:
                           mBluetoothAdapter.enable();
                           break;
                   }
               } catch (Exception e) {
               }
           }
       });

(5)GPS

这里我前面也提到了,直接跳转到系统的设置页面,startActivity(new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS));

ok,主要内容就这些,大家有什么问题可以在留言里面提出来~

源码下载地址

时间: 2024-10-16 10:27:20

自定义水波球清理内存的悬浮窗小工具的相关文章

Android 悬浮窗、悬浮球开发

原文:Android 悬浮窗.悬浮球开发 1.权限管理 直接看我另外一篇博客吧,传送门: https://my.oschina.net/u/1462828/blog/1933162 2.Base类BaseSuspend import android.content.Context; import android.graphics.PixelFormat; import android.os.Build; import android.view.Gravity; import android.vi

突破小米悬浮窗权限控制--不需要权限的悬浮窗

突破小米悬浮窗权限控制–不需要权限的悬浮窗 在上一篇文章讲了Android的Toast拓展,在原生Toast基础上对显示时长和显示动画做了二次封装,强化了Toast的部分功能.也分析了对于二次封装的ExToast设计原理,以及Toast的关键点.如果不了解的可以看看下面的链接. Toast拓展–自定义显示时间和动画 常用悬浮窗与Toast 之前分析过,Toast其实就是系统悬浮窗的一种,那它跟常用的系统悬浮窗有什么区别呢? 先看一下常用的Andoird系统悬浮窗写法: // 获取应用的Conte

anddroid悬浮窗的手机QQ后台清理不掉的秘密

问题来自于一篇文章:手机QQ后台为何清不掉?MIUI工程师:全靠1像素的页面保命出于好奇,想知道这一像素到底是啥东西,用手机安全管家控制QQ的悬浮窗权限:关闭QQ的悬浮窗权限,通过后台一键清理,重新打开QQ,发现是从splash开始的:打开QQ的悬浮窗权限,一键清理后,打开QQ,发现是直接进入主界面的:说明一键清理未清理QQ,或者清理之后,QQ又自启动了.至于,是未被清理还是自启动,后面再探究一下,这里就说说这个一像素的悬浮窗. Button button = new Button(getApp

Android开发笔记(一百一十八)自定义悬浮窗

WindowManager 在前面<Android开发笔记(六十六)自定义对话框>中,我们提到每个页面都是一个Window窗口,许多的Window对象需要一个管家来打理,这个管家我们称之为WindowManager窗口管理.在手机屏幕上新增或删除页面窗口,都可以归结为WindowManager的操作,下面是该管理类的常用方法说明: getDefaultDisplay : 获取默认的显示屏信息.通常用该方法获取屏幕分辨率,详情参见<Android开发笔记(三)屏幕分辨率>. addV

android悬浮窗--获取内存显示当前内存使用量

原文地址:http://www.android100.org/html/201306/20/3224.html 运行效果: 其中, 这一块就是悬浮窗,可以随意拖动,动态显示当前内存使用量. 下面看一下代码是如何实现的: 悬浮窗的实现是用了一个service,为什么要用service呢?了解service特点的大体就会明白.下面看一下: public class FloatService extends Service { WindowManager wm = null; WindowManage

手机QQ后台清理不掉的秘密——anddroid悬浮窗

问题来自于一篇文章:手机QQ后台为何清不掉?MIUI工程师:全靠1像素的页面保命 出于好奇,想知道这一像素到底是啥东西,用手机安全管家控制QQ的悬浮窗权限: 关闭QQ的悬浮窗权限,通过后台一键清理,重新打开QQ,发现是从splash开始的: 打开QQ的悬浮窗权限,一键清理后,打开QQ,发现是直接进入主界面的:说明一键清理未清理QQ,或者清理之后,QQ又自启动了. 至于,是未被清理还是自启动,后面再探究一下,这里就说说这个一像素的悬浮窗. Button button = new Button(ge

android桌面悬浮窗实现

                        首先是一个小的悬浮窗显示的是当前使用了百分之多少的内存,点击一下小悬浮窗,就会弹出一个大的悬浮窗,可以一键加速.好,我们现在就来模拟实现一下类似的效果. 1.新建一个项目 , 打开activity_main.xml <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.an

android桌面悬浮窗仿QQ手机管家加速效果

主要还是用到了WindowManager对桌面悬浮进行管理. 需要一个火箭的悬浮窗 一个发射台悬浮窗  ,判断火箭是否放到了发射台,如果放上了,则使用AsyTask 慢慢将火箭的图片往上移.结束后., 返回原位. 1.打开activity_main.xml <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.andro

Android进阶2之PopupWindow弹窗(有点悬浮窗的感觉)

PopupWindow是一个可以用来显示一个任意的视图的弹出窗口,他需要完全依赖layout布局. 它没什么界面,在弹出的窗口中完全显示布局中的控件. 上面两个美女头就是弹窗PopupWindow显示的内容.是两个Button. 具体实现: 注意:那三个Button不能和普通的Button一样通过findViewById()方法获得,必须首先说的Button所在的视图,View popview = layoutInflater.inflate(R.layout.poplayout, null);