English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية

Подражание Android 360 Speed Ball для реализации выпуска памяти

В настоящее время на телефонах все больше и больше приложений с плавающими окнами, для пользователей наиболее распространенными приложениями с плавающими окнами являются浮动ные små控件 в безопасности программ, например, в 360卫士, когда включен плавающий окно, это шарик, который можно тянуть, когда нажимаете на шарик, появляется большой окно控件, можно выполнять дальнейшие действия, такие как: освободить память телефона и т.д. Поэтому, воспользовавшись видео на Муку, имитируя реализацию 360 ускорительного шара, добавлена функция освободить память при нажатии на шарик.

Поскольку это телефон, только скриншоты экрана: после реализации如下图所示: Нажмите на кнопку включения, появится плавающий шарик控件, на котором отображается процент доступной памяти телефона; когда тянете шарик, он становится иконкой Android; отпустите шарик, он прилипает к бокам экрана; нажмите на шарик, появится большой окно控件 на нижней части телефона, нажмите на шарик в окне, чтобы освободить память телефона; нажмите на другие области экрана телефона, большое окно исчезнет, и шарик снова появится.

Результат таков:

Далее следуют важные шаги для реализации:

1. Реализация FloatCircleView (пользовательский view)

Процесс реализации FloatCircleView - это процесс создания пользовательского view. 1. Определение свойств пользовательского View 2. Получение пользовательских свойств в методе конструктора View 3. Переопределение onMeasure 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, если это не состояние перетаскивания, выполняется рисование шара. Рисование шара не составляет труда,关键是 рисование текста. Два следующих изображения помогут лучше понять процесс рисования текста.

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, если нет, отображается шар.
   * @param canvas
   */
  @Override
  protected void onDraw(Canvas canvas) {
    if (isDraging){
      canvas.drawBitmap(scaledBitmap, 0, 0, null);
    }
      //1. Нанесение круга
      canvas.drawCircle(width / 2, heigth / 2, width / 2, circlePaint);
      //2. Нанесение текста
      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; // Получение общей системной памяти, единицы измерения - КБ, умножение на 1024 для конвертации в Байт
      localBufferedReader.close();
    catch (IOException e) {
    }
    //return Formatter.formatFileSize(context, initial_memory); // Byte to KB or MB, normalize memory size
    return initial_memory / (1024 * 1024);
  }

2. Create a WindowManager window management class to manage the floating ball and the bottom large window.

WindowManager class. Used to manage the display and hide of the entire floating ball and the bottom large window of the phone.

You must add the permission <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" /> in the Manifest file.

Get the window management class by WindowManager wm = (WindowManager) getSystemService(Context.WINDOW_SERVICE);

Use wm.addView(view, params) to add the view to the window.

Use wm.remove(view, params) to remove the view from the window.

Use wm.updateViewLayout(view, params) to update the view.

WindowManager.LayoutParams is used to set various properties of the view.

1. Create an instance of FloatViewManager.

//Singleton pattern creation
 public static FloatViewManager getInstance(Context context) {
    if (inStance == null) {
      synchronized(FloatViewManager.class) {
        if (inStance == null) {
          inStance = new FloatViewManager(context);
        }
      }
    }
    return inStance;
  }

2. Display floating ball and methods to display the bottom window. (The method to display the window is similar to displaying the floating ball.)

/**
   * Display floating window
   */
  public void showFloatCircleView() {
  //Parameter settings
    if (params == null) {
      params = new WindowManager.LayoutParams();
      //Width and height
      params.width = circleView.width;
      params.height = circleView.heigth;
      //Alignment
      params.gravity = Gravity.TOP | Gravity.LEFT;
      // Позиция
      params.x=0;
      params.y = 0;
      // Тип
      params.type = WindowManager.LayoutParams.TYPE_TOAST;
      // Настройка свойств окна.
      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 и половины ширины экрана телефона делается вывод, где шарик прижат к левому или правому краю экрана. Затем идет ошибка при перетаскивании шара. При перетаскивании шара на расстояние менее 10 пикселей можно активировать событие клика по шарику. (Событие Touch для шара имеет приоритет перед событием клика по шарику. Если событие Touch возвращает true, это событие потребляется и не передается дальше. Если событие Touch возвращает false, это событие продолжает передаваться и активирует событие клика по шарику.)

Событие клика по шарику: при клике по шарику, шарик исчезает, на нижней части телефона появляется окно. И есть анимация перехода при появлении окна.

//Для установки слушателя touch для circleView.
  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;
          }
            params.x=0;
          }
          circleView.setDrageState(false);
          wm.updateViewLayout(circleView,params);
          if (Math.abs(moveX-downX)>10){
            return true;
          }
            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);
      }
        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);
      }
        handler.removeCallbacks(runnbale);
        //Освободите память.
       killprocess();
       //Рассчитайте процент использованного после выпуска памяти.
        num = (int)(((float)currentProgress / max) * 100);
      }
    }
  }

3. Перерисовка при单击 и двойном щелчке.

Сначала нарисуйте шарик и путь волн.
//Нарисуйте шарик
    bitmapCanvas.drawCircle(width / 2, height / 2, width / 2, circlepaint);
    //На основе path, нарисуйте путь волн. Перед каждым рисованием сбросьте предыдущий path, reset.
    path.reset();
    float y = (1 - (float)num / 100) * height;
    path.moveTo(width, y);
    path.lineTo(width, height);
    path.lineTo(0, height);
    path.lineTo(0, y);

Далее используйте кривые Безье для рисования пути волн.

Android-кривые Безье

Применение кривых Безье в Android

Здесь есть详细的 объяснение кривых Безье. На самом деле не нужно углубляться. Достаточно знать, что можно использовать их для реализации эффекта волн. Кривые Безье имеют множество применений, эффект перелистывания книги также можно реализовать с их помощью. Основное использование path.rQuadTo(x1, y1, x2, y2); конец (x2, y2), вспомогательные точки контроля (x1, y1) кривые Безье. Таким образом, изменяя положение y1, можно нарисовать эффект волн.

Сначала проверьте, является ли это двойным щелчком:

Если это двойной щелчок: установите переменную d, изменяя её постоянно (изменение значения d вызывается num, а num действительно уменьшается в handler. num--;), чтобы нарисовать кривые Безье. Реализовать эффект снижения волн.

Jeśli jest to kliknięcie: установите значение count, изменяя его постоянно (изменение значения count реализуется в handler. count--;), сначала проверьте,是否能 быть count делится на 2, чередуя рисование этих двух кривых Безье. (Эти две кривые Безье обратны), чтобы реализовать эффект волнения воды.
(Использование цикла for для реализации числа волн, пара path.rQuadTo(); может реализовать только одну волну. Możesz samodzielnie potwierdzić)

 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);
      float d = (float) count / 50 * 10;
       }
    }
      if (count % 2 == 0) {
      else {
        for (int i = 0; i <= 3; i++) {
          path.rQuadTo(10, -d, 30, 0);
          path.rQuadTo(10, d, 30, 0);
        }
      }
        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);)

Используя метод 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 есть три варианта. (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 есть три варианта, такие как первый параметр.

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

Вот и все, что есть в этой статье, я надеюсь, что это поможет вам в изучении, и希望大家多多支持呐喊教程。

Декларация: контент статьи взят из Интернета, авторские права принадлежат соответствующему автору, контент предоставлен пользователями Интернета в порядке добровольного участия и самостоятельной загрузки, сайт не обладает правами собственности, не производится редактирование вручную и не несет ответственности за соответствующие юридические вопросы. Если вы обнаружите подозрительный контент, пожалуйста, отправьте письмо по адресу: notice#oldtoolbag.com (при отправке письма, пожалуйста, замените # на @) для сообщения о нарушении и предоставьте соответствующие доказательства. При обнаружении факта нарушения, сайт немедленно удаляет подозрительный контент.

Основной учебник
Вам может понравиться