欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

android仿360加速球实现内存释放

程序员文章站 2024-03-04 13:27:17
现在手机上的悬浮窗应用越来越多,对用户来说,最常见的悬浮窗应用就是安全软件的悬浮小控件,拿360卫士来说,当开启悬浮窗时,它是一个小球,小球可以拖动,当点击小球出现大窗体控...

现在手机上的悬浮窗应用越来越多,对用户来说,最常见的悬浮窗应用就是安全软件的悬浮小控件,拿360卫士来说,当开启悬浮窗时,它是一个小球,小球可以拖动,当点击小球出现大窗体控件,可以进行进一步的操作如:释放手机内存等等。于是借着慕课网的视频,仿着实现了360加速球,增加了点击小球进行释放内存的功能。

由于是手机只有频幕截图:实现后如下图所示:点击开启按钮,出现悬浮窗小球控件上面显示手机的可用内存百分比;当拖动小球时,小球变为android图标;松开小球,小球依附在频幕两侧;点击小球,手机底部出现大窗体控件,点击里面的小球,进行手机内存的释放;点击手机屏幕的其他区域,大窗体消失,小球重新出现。

效果如下:

android仿360加速球实现内存释放android仿360加速球实现内存释放

接下来就是实现的一些重要步骤:

1.floatcircleview的实现(自定义view)

实现floatcircleview的过程就是自定义view的过程。1、自定义view的属性 2、在view的构造方法中获得我们自定义的属性 3、重写onmesure 4、重写ondraw。我们没有自定义其他属性所以省了好多步骤。

各种变量的初始化,设置拖动小球时要显示的图标,已经计算各种内存。(用于显示在小球上)
 

 public int width=100;
  public int heigth=100;
  private paint circlepaint;//画圆
  private paint textpaint; //画字
  private float availmemory; //已用内存
  private float totalmemory; //总内存
  private string text;  //显示的已用内存百分比
  private boolean isdraging=false; //是否在拖动状态。
  private bitmap src;
  private bitmap scaledbitmap; //缩放后的图片。
 /**
   * 初始化画笔以及计算可用内存,总内存,和可用内存百分比。
   */
  public void initpatints() {
    circlepaint = new paint();
    circlepaint.setcolor(color.cyan);
    circlepaint.setantialias(true);
    textpaint = new paint();
    textpaint.setcolor(color.white);
    textpaint.settextsize(25);
    textpaint.setfakeboldtext(true);
    textpaint.setantialias(true);
    //设置图片
    src = bitmapfactory.decoderesource(getresources(), r.mipmap.ic_launcher);
    //缩放后的图片(将图标设置的和悬浮小球一样大小。)
    scaledbitmap = bitmap.createscaledbitmap(src, width, heigth, true);
    //计算已用内存,总内存,已用内存百分比,
    availmemory= (float) getavailmemory(getcontext());
    totalmemory= (float) gettotalmemory(getcontext());
    text=(int)((availmemory/totalmemory)*100)+"%";
  }

onmeasure();就是将固定的宽高写死,通过 setmeasureddimension(width, heigth);传入。
ondraw();进行悬浮小球绘制。定义一个boolean变量判断当前状态是否为拖动小球状态,如果是拖动小球状态,就在该位置绘制android图标,如果不是拖动状态,就进行小球绘制。画小球没有难度,关键是画字。下面的2个图可以加深对画字时的理解。

android仿360加速球实现内存释放

1.画字时的x坐标(1.textpaint.measuretext(text);得到字的宽度2.小球的宽度/2-字的宽度/2。)
2.画字时的y坐标(1.paint.fontmetrics fontmetrics = textpaint.getfontmetrics();得到字体属性测量类。2.(fontmetrics.ascent + fontmetrics.descent) / 2 得到字的高度。3.小球的高度/2-字体的高度/2)

画个图就很好理解了:

android仿360加速球实现内存释放

/**
   * 画小球及文字。如果小球是在拖动状态就显示android图标,如果不是拖动状态就显示小球。
   * @param canvas
   */
  @override
  protected void ondraw(canvas canvas) {
    if (isdraging){
      canvas.drawbitmap(scaledbitmap,0,0,null);
    }else {
      //1.画圆
      canvas.drawcircle(width / 2, heigth / 2, width / 2, circlepaint);
      //2.画text
      float textwidth = textpaint.measuretext(text);//文本宽度
      float x = width / 2 - textwidth / 2;
      paint.fontmetrics fontmetrics = textpaint.getfontmetrics();

      float dy = -(fontmetrics.ascent + fontmetrics.descent) / 2;
      float y = heigth / 2 + dy;
      canvas.drawtext(text, x, y, textpaint);
    }



  }

获得手机已用内存及总内存的方法:
   

public long getavailmemory(context context)
  {
    // 获取android当前可用内存大小
    activitymanager am = (activitymanager) context.getsystemservice(context.activity_service);
    activitymanager.memoryinfo mi = new activitymanager.memoryinfo();
    am.getmemoryinfo(mi);
    //mi.availmem; 当前系统的可用内存
    //return formatter.formatfilesize(context, mi.availmem);// 将获取的内存大小规格化

    return mi.availmem/(1024*1024);
  }
  public long gettotalmemory(context context)
  {
    string str1 = "/proc/meminfo";// 系统内存信息文件
    string str2;
    string[] arrayofstring;
    long initial_memory = 0;
    try
    {
      filereader localfilereader = new filereader(str1);
      bufferedreader localbufferedreader = new bufferedreader(
          localfilereader, 8192);
      str2 = localbufferedreader.readline();// 读取meminfo第一行,系统总内存大小
      arrayofstring = str2.split("\\s+");
      for (string num : arrayofstring) {
        log.i(str2, num + "\t");
      }
      initial_memory = integer.valueof(arrayofstring[1]).intvalue() * 1024;// 获得系统总内存,单位是kb,乘以1024转换为byte
      localbufferedreader.close();
    } catch (ioexception e) {
    }
    //return formatter.formatfilesize(context, initial_memory);// byte转换为kb或者mb,内存大小规格化

    return initial_memory/(1024*1024);
  }

2.创建windowmanager窗体管理类,管理悬浮小球和底部大窗体。

windowmanager类。用来管理整个悬浮小球和手机底部大窗体的显示和隐藏。

必须在manifest文件中增加<uses-permission android:name="android.permission.system_alert_window" />权限。

通过 windowmanager wm = (windowmanager)getsystemservice(context.window_service);获取窗体管理类;

利用wm.addview(view, params);将view增加到窗体中。

利用wm.remove(view,params);将view从窗体中移除。

利用wm.updateviewlayout(view,params);来更新view.

windowmanager.layoutparams用来设置view的各种属性。

1.创建floatviewmanager实例。

//单例模式创建
 public static floatviewmanager getinstance(context context){
    if (instance==null){
      synchronized(floatviewmanager.class){
        if (instance==null){
          instance=new floatviewmanager(context);
        }
      }
    }
    return instance;
  }

2.展示悬浮小球和展示底部窗体的方法。(展示窗体的方法同展示悬浮小球类似。)

/**
   * 展示浮窗
   */
  public void showfloatcircleview(){
  //参数设置
    if (params==null){
      params = new windowmanager.layoutparams();
      //宽高
      params.width=circleview.width;
      params.height=circleview.heigth;
      //对齐方式
      params.gravity= gravity.top|gravity.left;
      //偏移量
      params.x=0;
      params.y=0;
      //类型
      params.type=windowmanager.layoutparams.type_toast;
      //设置该window属性。
      params.flags= windowmanager.layoutparams.flag_not_focusable| windowmanager.layoutparams.flag_not_touch_modal;
      //像素格式
      params.format= pixelformat.rgba_8888;
    }
    //将小球加入窗体中。
    wm.addview(circleview, params);
  }

 public void showfloatcircleview(){
 ......
 }

 3.当启动程序,首先创建悬浮小球,小球可以拖拽,点击小球,手机底部窗体显示(floatmenuview),小球隐藏。所以,对小球(circleview)要对其进行setontouchlistener和setonclicklistener事件监听。

分析小球的事件分发; 对于小球:

当action_down时,记录小球的downx,downy,以及startx,starty,

当action_move时,将circleview是否拖拽状态置为true,记录小球的movex,movey,计算小球移动的距离(dx,dy),然后根据 wm.updateviewlayout(circleview,params);更新小球位置。最后将最后move的坐标赋值给startx,starty。

当action_up时,将circleview是否拖拽置为false,记录抬起时的坐标,upx,根据upx和手机屏幕宽度/2,进行判断,来觉得最终小球是贴在屏幕左侧,还是右侧。后面为小球拖拽的误差。当小球拖拽的距离小于10个像素时,可以触发小球的点击事件。(小球的touch事件,优先于小球的点击事件,当touch事件返回true时,此事件被消费,不再向下传递事件。当touch事件返回false时,此事件继续向下传递,从而触发小球的点击事件。)

小球的点击事件:点击小球,悬浮小球隐藏,手机底部窗体出现。并设置有底部窗体出现时的过渡动画。

//给circleview设置touch监听。
  private view.ontouchlistener circleviewontouchlistener=new view.ontouchlistener() {
    @override
    public boolean ontouch(view v, motionevent event) {
      switch (event.getaction()){
        case motionevent.action_down:
        //最后按下时的坐标,根据action_move理解。
          startx = event.getrawx();
          starty = event.getrawy();
          //按下时的坐标。
          downx = event.getrawx();
          downy = event.getrawy();
          break;
        case motionevent.action_move:
          circleview.setdragestate(true);
           movex = event.getrawx();
           movey=event.getrawy();
          float dx = movex -startx;
          float dy=movey-starty;
          params.x+=dx;
          params.y+=dy;
          wm.updateviewlayout(circleview,params);
          startx= movex;
          starty=movey;
          break;
        case motionevent.action_up:
          float upx=event.getrawx();
          if (upx>getscreenwidth()/2){
            params.x=getscreenwidth()-circleview.width;
          }else {
            params.x=0;
          }
          circleview.setdragestate(false);
          wm.updateviewlayout(circleview,params);
          if (math.abs(movex-downx)>10){
            return true;
          }else {
            return false;
          }
        default:
          break;
      }
      return false;
    }
  };
circleview.setontouchlistener(circleviewontouchlistener);
    circleview.setonclicklistener(new view.onclicklistener() {
      @override
      public void onclick(view v) {
        //toast.maketext(, "onclick", toast.length_short).show();
        //隱藏circleview,顯示菜单栏。
        wm.removeview(circleview);
        showfloatmenuview();
        floatmenuview.startanimation();
      }
    });

3.myprogreeview(手机底部窗体中小球的实现)。

1.初始化画笔,对view进行手势监听。监听单击和双击事件。(必须设置view是可以点击的)

private void initpaint() {
    //画圆画笔
    circlepaint = new paint();
    circlepaint.setcolor(color.argb(0xff, 0x3a, 0x8c, 0x6c));
    circlepaint.setantialias(true);
    //画进度条画笔
    progersspaint = new paint();
    progersspaint.setantialias(true);
    progersspaint.setcolor(color.argb(0xff, 0x4e, 0xcc, 0x66));
    progersspaint.setxfermode(new porterduffxfermode(porterduff.mode.src_in));//绘制重叠部分
    //画进度画笔
    textpaint = new paint();
    textpaint.setantialias(true);
    textpaint.setcolor(color.white);
    textpaint.settextsize(25);

    //画布
    bitmap = bitmap.createbitmap(width, heigth, bitmap.config.argb_8888);
    bitmapcanvas = new canvas(bitmap);
    //手势监听。
    gesturedetector = new gesturedetector(new mygerturedetectorlistener());
    setontouchlistener(new ontouchlistener() {
      @override
      public boolean ontouch(view v, motionevent event) {
        return gesturedetector.ontouchevent(event);
      }
    });
    //设置view可以点击。
    setclickable(true);
  }
  class mygerturedetectorlistener extends gesturedetector.simpleongesturelistener{
    @override
    public boolean ondoubletap(motionevent e) {
    ......
     //双击事件的逻辑
      return super.ondoubletap(e);
    }

    @override
    public boolean onsingletapconfirmed(motionevent e) {
    ......
     //单击事件的逻辑
      return super.onsingletapconfirmed(e);
    }
  }

2.用handler交互进行单击和双击事件的状态更新。单击时,利用贝塞尔曲线,实现波纹荡漾效果。双击时,波纹不断下降,进行内存释放,最后显示内存释放后的已用内存百分比。handler发送周期消息,让单击事件和双击事件的小球不断进行重绘。(重绘在下一小节讲)。

//单击事件发送周期handler.
private void startsingletapanimation() {
    handler.postdelayed(singletaprunnable,200); 
  }
  private singletaprunnable singletaprunnable=new singletaprunnable();
  class singletaprunnable implements runnable{
    @override
    public void run() {
      count--;
      if (count>=0) {
        invalidate();//不断进行重绘。
        handler.postdelayed(singletaprunnable,200);
      }else {
        handler.removecallbacks(singletaprunnable);
        count=50;
      }
    }
  }
  //双击事件发送周期handler。
  private void startdoubletapanimation() {
    handler.postdelayed(runnbale,50);
  }
  private doubletaprunnable runnbale=new doubletaprunnable();

  class doubletaprunnable implements runnable{
    @override
    public void run() {
      num--;
      if (num>=0){
        invalidate();//不断进行重绘。
        handler.postdelayed(runnbale,50);
      }else {
        handler.removecallbacks(runnbale);
        //释放内存。
       killprocess();
       //计算释放后的已用内存百分比。
        num=(int)(((float)currentprogress/max)*100);
      }
    }
  }

3.单击事件和双击事件的重绘。

首先是小球的绘制,和波纹路径的绘制。
//绘制小球
    bitmapcanvas.drawcircle(width / 2, heigth / 2, width / 2, circlepaint);
    //根据path,绘制波纹路径。每次绘制前将上次的path,reset.
    path.reset();
    float y =(1-(float)num/100)*heigth;
    path.moveto(width, y);
    path.lineto(width, heigth);
    path.lineto(0, heigth);
    path.lineto(0, y);

接着利用贝塞尔曲线将波纹路径绘制。

android-贝塞尔曲线

贝塞尔曲线在android中的应用

这里有详细的讲解贝塞尔曲线。其实不需要深入的理解。只要知道能用它来实现水波纹效果就行了(贝塞尔曲线用处很多,翻书效果也可以用它实现。)主要利用 path.rquadto(x1,y1,x2,y2); 终点(x2,y2),辅助控制点(x1,y1)的贝塞尔曲线。因此,通过不断改变y1的位置,我们可以绘制出水波纹的效果。

首先判断它是否为双击击事件:

若是双击:设置一个变量d,通过不断改变d的值(d的值的改变由num引起,而num实在handler中不断减小的。num–;),来绘制贝塞尔曲线。实现水波纹的下降效果。

若是单击:设置一个count值,通过不断改变count值(count值的改变是在handler中实现的。count–;),首先判断count是否能被2整除,交替绘制这两条贝塞尔曲线。(这两条贝塞尔曲线正好相反),从而实现水波荡漾的效果。
(用for循环是实现水波的波数,一对path.rquadto();只能实现一次波纹。可以自己去验证)

 if (!issingletap){
    float d=(1-(float)num/(100/2))*10;
      for (int i=0;i<3;i++){
      path.rquadto(10,-d,20,0);
      path.rquadto(10,d,20,0);
       }
    }else {
      float d=(float)count/50*10;
      if (count%2==0){
        for (int i=0;i<=3;i++){
          path.rquadto(10,-d,30,0);
          path.rquadto(10,d,30,0);
        }
      }else {
        for (int i=0;i<=3;i++){
          path.rquadto(10,d,30,0);
          path.rquadto(10,-d,30,0);
        }

      }
    }

最后是释放内存的方法。记得要在manifest文件中增加<uses-permission android:name="android.permission.kill_background_processes"/>权限。
 

public void killprocess(){
    activitymanager activitymanger=(activitymanager) getcontext().getsystemservice(context.activity_service);
    list<activitymanager.runningappprocessinfo> list=activitymanger.getrunningappprocesses();
    if(list!=null)
      for(int i=0;i<list.size();i++)
      {
        activitymanager.runningappprocessinfo apinfo=list.get(i);
        string[] pkglist=apinfo.pkglist;
 if(apinfo.importance>activitymanager.runningappprocessinfo.importance_service)
        {
          // process.killprocess(apinfo.pid);
          for(int j=0;j<pkglist.length;j++) {
            boolean flag=pkglist[j].contains("com.example.yyh.animation360");//这里要判断是否为当前应用,要不然也可能会结束当前应用。
            if(!flag){
            activitymanger.killbackgroundprocesses(pkglist[j]);
          }
          }
        }
      }

4.floatmenuview的实现。

1.创建一个float_menuview.xml;其中包括一个imageview+textview+自定义的myprogreeview。
底部窗体要被设置能被点击。android:clickable="true";

<?xml version="1.0" encoding="utf-8"?>
<relativelayout xmlns:android="http://schemas.android.com/apk/res/android"
  android:orientation="vertical"
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  android:background="#33000000"
  >
  <linearlayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical"
    android:background="#f02f3942"
    android:layout_alignparentbottom="true"
    android:id="@+id/ll"
    android:clickable="true"
    >
    <linearlayout
      android:layout_width="match_parent"
      android:layout_height="wrap_content"
      android:orientation="horizontal"
      >
      <imageview
        android:layout_width="50dp"
        android:layout_height="50dp"
        android:src="@mipmap/ic_launcher"
        android:layout_gravity="center_vertical"
        />
      <textview
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textsize="15sp"
        android:textcolor="#c93944"
        android:text="360加速球"
        android:layout_gravity="center_vertical"
        />
    </linearlayout>
    <com.example.yyh.animation360.view.myprogreeview
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:layout_gravity="center_horizontal"
      android:layout_margintop="10dp"
      />
  </linearlayout>
</relativelayout>

2.将floatmenuview 根据条件,利用(wm.addview(view, params);将view增加到窗体中。

利用wm.remove(view,params);将view从窗体中移除。)方法,进行底部窗体view的显示和隐藏
translateanimation类用来设置底部窗体进入时的动画效果。translateanimation(int fromxtype,float fromxvalue,int toxtype,float toxvalue,int fromytype,float fromyvalue,int toytype,float toyvalue)
int fromxtype:x轴方向起始的参照值有3个选项。(1.animation.absolute:具体的坐标值,指绝对的屏幕像素单位。

2.animation.relative_to_self:相对自己的坐标值。3.animation.relative_to_parent:相对父容器的坐标值。)
float fromxvalue 第二个参数是第一个参数类型的起始值(例如若第一个参数设置为animation.relative_to_self,第二个参数为0.1f,就表示为自己的坐标值乘以0.1);

int toxtype:x轴方向终点的参照值有3个选项同第一个参数。

float tovalue:第四个参数是第三个参数类型的起始值。

y轴方向的参数同理。起点+终点;(每个参数后一个参数为前一个参数的起始值。)

并对此view设置ontouchlistener,ontouch事件最后必须返回false,表示此事件仍然需要向下传递。从而实现点击手机其他区域时,手机底部窗体隐藏,悬浮小球显示,点击底部窗体时无变化,点击底部窗体中的小球时,触发其单击和双击事件。

 view view =view.inflate(getcontext(), r.layout.float_menuview,null);
    linearlayout linearlayout= (linearlayout) view.findviewbyid(r.id.ll);
    translateanimation = new translateanimation(animation.relative_to_self,0,animation.relative_to_self,0,animation.relative_to_self,1.0f,animation.relative_to_self,0);
    translateanimation.setduration(500);
    translateanimation.setfillafter(true);
    linearlayout.setanimation(translateanimation);
    view.setontouchlistener(new ontouchlistener() {
      @override
      public boolean ontouch(view v, motionevent event) {
        floatviewmanager manager=floatviewmanager.getinstance(getcontext());
        manager.hidefloatmenuview();
        manager.showfloatcircleview();
        return false;
      }
    });
    addview(view);

5.myfloatservice

用来创建floatviewmanager单例,管理悬浮小球+手机底部窗体的创建和移除。

public class myfloatservice extends service {
  @nullable
  @override
  public ibinder onbind(intent intent) {
    return null;
  }
  @override
  public void oncreate() {
    //用来开启floatviewmanager
    floatviewmanager manager=floatviewmanager.getinstance(this);
    manager.showfloatcircleview();
    super.oncreate();
  }

}

6.mainactivity的实现

定义一个intent,开启服务(在服务中创建windowmanager单例对象,进行悬浮小球和手机底部窗体的管理。),关闭当前的activity。

public class mainactivity extends appcompatactivity {
  @override
  protected void oncreate(bundle savedinstancestate) {
    super.oncreate(savedinstancestate);
    setcontentview(r.layout.activity_main);
  }
  public void startservice(view view){
    intent intent=new intent(this, myfloatservice.class);
    startservice(intent);
    finish();
  }
}

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。