+4

Hướng dẫn tạo ứng dụng sử dụng Floating Widget giống Facebook Messenger

Floating widgets là view trôi trên màn hình. Nó rất thuận tiện cho việc thao tác đa nhiệm, 1 người có thể làm việc trên các ứng dụng khác nhau và kiểm soát chúng cùng 1 lúc. Điều đó có nghĩa là nếu bạn đang ở trong các ứng dụng tính toán và 1 widget từ máy nghe nhạc đang nổi trên màn hình, bạn có thể kiểm soát nhạc của bạn cùng một lúc

Trong hướng dẫn này, chúng ta sẽ tìm hiểu làm thế nào để tạo widget nổi đơn giản và cho phép người sử dụng để kéo chúng trên màn hình. Người dùng có thể điều chỉnh vị trí của widget trôi nổi trên màn hình. Chúng ta sẽ phát triển một floating nổi đó là có nút để điều khiển âm nhạc.

Xem trước kết quả

1. Hiểu khái quát

Hệ thống Android cho phép các ứng dụng vẽ trên ứng dụng khác nếu ứng dụng có quyền android.permission.SYSTEM_ALERT_WINDOW. Chúng tôi sẽ sử dụng các dịch vụ nền để thêm các widget nổi vào hệ thống phân cấp điểm của màn hình hiện tại. Vì vậy, floating view luôn luôn trên cùng của cửa sổ ứng dụng.

Để kéo thả lên trên màn hình chúng ta sẽ override OnTouchListener () để lắng nghe sự kiện kéo thả và sự thay đổi vị trí của các điểm có trong màn hình.

2. Hướng dẫn tạo project

Bước 1. Tạo mới ứng dụng trong Android Studio: File ⇒ New Project

Bước 2. Download resource và thêm chúng vào thư mục của project. Thư mục này có chứa nhưng icon, ảnh cần thiết cho ứng dụng demo này

Bước 3. Thêm quyền android.permission.SYSTEM_ALERT_WINDOW permission vào file AndroidManifest.xml file. Quyền này cho phép một ứng dụng có thể tạo cửa sổ, hiển thị nó lên trên top của tất cả các ứng dụng khác. Và chúng ta cũng sẽ thêm tên của dịch vụ FloatingViewService

AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
          package="info.androidhive.floatingview">
     
    <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
 
    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name="info.androidhive.floatingview.MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>
 
                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>
        </activity>
 
        <service
            android:name="info.androidhive.floatingview.FloatingViewService"
            android:enabled="true"
            android:exported="false"/>
    </application>
 
</manifest>

Bước 4. Tạo 1 layout với tên: layout_floating_widget.xml cho floating view. Layout này sẽ bao gồm 2 phần chính:

Collapsed view: floating widget sẽ co lại khi view được launched. Khi người dùng click vào view này này, nó sẽ đượcc mở rộng ra.

Expanded View: view này sẽ chứa các nút để chơi nhạc, thay đổi bài hát: next / previous và mở ứng dụng.

layout_floating_widget.xml
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content">
 
    <!--Root container-->
    <RelativeLayout
        android:id="@+id/root_container"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        tools:ignore="UselessParent">
 
        <!--View while view is collapsed-->
        <RelativeLayout
            android:id="@+id/collapse_view"
            android:layout_width="wrap_content"
            android:visibility="visible"
            android:layout_height="wrap_content"
            android:orientation="vertical">
 
            <!--Icon of floating widget -->
            <ImageView
                android:id="@+id/collapsed_iv"
                android:layout_width="60dp"
                android:layout_height="60dp"
                android:layout_marginTop="8dp"
                android:src="@drawable/ic_android_circle"
                tools:ignore="ContentDescription"/>
 
            <!--Close button-->
            <ImageView
                android:id="@+id/close_btn"
                android:layout_width="20dp"
                android:layout_height="20dp"
                android:layout_marginLeft="40dp"
                android:src="@drawable/ic_close"
                tools:ignore="ContentDescription"/>
        </RelativeLayout>
 
        <!--View while view is expanded-->
        <LinearLayout
            android:id="@+id/expanded_container"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:background="#F8BBD0"
            android:visibility="gone"
            android:orientation="horizontal"
            android:padding="8dp">
 
            <!--Album image for the song currently playing.-->
            <ImageView
                android:layout_width="80dp"
                android:layout_height="80dp"
                android:src="@drawable/music_player"
                tools:ignore="ContentDescription"/>
 
            <!--Previous button-->
            <ImageView
                android:id="@+id/prev_btn"
                android:layout_width="30dp"
                android:layout_height="30dp"
                android:layout_gravity="center_vertical"
                android:layout_marginLeft="20dp"
                android:src="@mipmap/ic_previous"
                tools:ignore="ContentDescription"/>
 
            <!--Play button-->
            <ImageView
                android:id="@+id/play_btn"
                android:layout_width="50dp"
                android:layout_height="50dp"
                android:layout_gravity="center_vertical"
                android:layout_marginLeft="10dp"
                android:src="@mipmap/ic_play"
                tools:ignore="ContentDescription"/>
 
            <!--Next button-->
            <ImageView
                android:id="@+id/next_btn"
                android:layout_width="30dp"
                android:layout_height="30dp"
                android:layout_gravity="center_vertical"
                android:layout_marginLeft="10dp"
                android:src="@mipmap/ic_play_next"
                tools:ignore="ContentDescription"/>
 
            <RelativeLayout
                android:layout_width="wrap_content"
                android:layout_height="match_parent"
                android:orientation="vertical">
 
                <ImageView
                    android:id="@+id/close_button"
                    android:layout_width="20dp"
                    android:layout_height="20dp"
                    android:src="@drawable/ic_close"/>
 
                <ImageView
                    android:id="@+id/open_button"
                    android:layout_width="20dp"
                    android:layout_height="20dp"
                    android:layout_alignParentBottom="true"
                    android:src="@drawable/ic_open"/>
            </RelativeLayout>
        </LinearLayout>
    </RelativeLayout>
</FrameLayout>

2.1. Thêm Floating Widget (Music Controls) and xử ký kéo thả

Bây giờ tạo ra một dịch vụ có tên là FloatingViewService.java. Bất cứ khi nào bạn muốn hiển thị floating view, bắt đầu sử dụng dịch vụ bằng lệnh startService (). Trong onCreate () của dịch vụ, chúng ta sẽ bổ sung thêm lauout của floating view ở góc trên bên trái của cửa sổ.

FloatingViewService.java
public class FloatingViewService extends Service {
   private WindowManager mWindowManager;
   private View mFloatingView;
 
   public FloatingViewService() {
   }
 
   @Override
   public IBinder onBind(Intent intent) {
       return null;
   }
 
   @Override
   public void onCreate() {
       super.onCreate();
       //Inflate the floating view layout we created
       mFloatingView = LayoutInflater.from(this).inflate(R.layout.layout_floating_widget, null);
 
       //Add the view to the window.
       final WindowManager.LayoutParams params = new WindowManager.LayoutParams(
               WindowManager.LayoutParams.WRAP_CONTENT,
               WindowManager.LayoutParams.WRAP_CONTENT,
               WindowManager.LayoutParams.TYPE_PHONE,
               WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
               PixelFormat.TRANSLUCENT);
 
       //Specify the view position
       params.gravity = Gravity.TOP | Gravity.LEFT;        //Initially view will be added to top-left corner
       params.x = 0;
       params.y = 100;
 
       //Add the view to the window
       mWindowManager = (WindowManager) getSystemService(WINDOW_SERVICE);
       mWindowManager.addView(mFloatingView, params);
 
       //….
       //….
   }
 
 
   @Override
   public void onDestroy() {
       super.onDestroy();
       if (mFloatingView != null) mWindowManager.removeView(mFloatingView);
   }
} 

Ngoài ra, chúng ta thực hiện OnClickListner () cho tất cả các nút như chơi, tạm dừng, và ứng dụng mở. Bạn có thể thực hiện một chức năng chơi nhạc tạm dừng trên nút bấm sử dụng MediaPlayer. Khi người dùng nhấp vào nút đóng ở collapsed view, FloatingViewService sẽ bị destroyed và floating view sẽ được loại bỏ khỏi hệ thống phân cấp xem.

Thêm mã dưới đây ở FloatingViewService.java trong phương thức onCrate ().

FloatingViewService.java
//The root element of the collapsed view layout
final View collapsedView = mFloatingView.findViewById(R.id.collapse_view);
//The root element of the expanded view layout
final View expandedView = mFloatingView.findViewById(R.id.expanded_container);
 
 
//Set the close button
ImageView closeButtonCollapsed = (ImageView) mFloatingView.findViewById(R.id.close_btn);
closeButtonCollapsed.setOnClickListener(new View.OnClickListener() {
   @Override
   public void onClick(View view) {
       //close the service and remove the from from the window
       stopSelf();
   }
});
 
 
//Set the view while floating view is expanded.
//Set the play button.
ImageView playButton = (ImageView) mFloatingView.findViewById(R.id.play_btn);
playButton.setOnClickListener(new View.OnClickListener() {
   @Override
   public void onClick(View v) {
       Toast.makeText(FloatingViewService.this, "Playing the song.", Toast.LENGTH_LONG).show();
   }
});
 
 
//Set the next button.
ImageView nextButton = (ImageView) mFloatingView.findViewById(R.id.next_btn);
nextButton.setOnClickListener(new View.OnClickListener() {
   @Override
   public void onClick(View v) {
       Toast.makeText(FloatingViewService.this, "Playing next song.", Toast.LENGTH_LONG).show();
   }
});
 
 
//Set the pause button.
ImageView prevButton = (ImageView) mFloatingView.findViewById(R.id.prev_btn);
prevButton.setOnClickListener(new View.OnClickListener() {
   @Override
   public void onClick(View v) {
       Toast.makeText(FloatingViewService.this, "Playing previous song.", Toast.LENGTH_LONG).show();
   }
});
 
 
//Set the close button
ImageView closeButton = (ImageView) mFloatingView.findViewById(R.id.close_button);
closeButton.setOnClickListener(new View.OnClickListener() {
   @Override
   public void onClick(View view) {
       collapsedView.setVisibility(View.VISIBLE);
       expandedView.setVisibility(View.GONE);
   }
});
 
 
//Open the application on thi button click
ImageView openButton = (ImageView) mFloatingView.findViewById(R.id.open_button);
openButton.setOnClickListener(new View.OnClickListener() {
   @Override
   public void onClick(View view) {
       //Open the application  click.
       Intent intent = new Intent(FloatingViewService.this, MainActivity.class);
       intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
       startActivity(intent);
 
 
       //close the service and remove view from the view hierarchy
       stopSelf();
   }
});

Để kéo thả floating view cùng với việc chạm của người sử dụng, chúng ta phải ghi đè OnTouchListener (). Bất cứ khi nào người dùng chạm vào thư mục gốc của view, chúng ta sẽ ghi lại các tọa độ giá trị khởi tạo x, y, và khi người dùng di chuyển các ngón tay, ứng dụng sẽ tính toán tọa độ X mới và tọa độ Y phối hợp và di chuyển float view.

FloatingViewService.java
//Drag and move floating view using user's touch action.
mFloatingView.findViewById(R.id.root_container).setOnTouchListener(new View.OnTouchListener() {
   private int initialX;
   private int initialY;
   private float initialTouchX;
   private float initialTouchY;
 
 
   @Override
   public boolean onTouch(View v, MotionEvent event) {
       switch (event.getAction()) {
           case MotionEvent.ACTION_DOWN:
 
 
               //remember the initial position.
               initialX = params.x;
               initialY = params.y;
 
 
               //get the touch location
               initialTouchX = event.getRawX();
               initialTouchY = event.getRawY();
               return true;
           case MotionEvent.ACTION_MOVE:
               //Calculate the X and Y coordinates of the view.
               params.x = initialX + (int) (event.getRawX() - initialTouchX);
               params.y = initialY + (int) (event.getRawY() - initialTouchY);
 
 
               //Update the layout with new X & Y coordinate
               mWindowManager.updateViewLayout(mFloatingView, params);
               return true;
       }
       return false;
   }
});

2.2. Xử lý Collapsing và Expanding của Floating Widget

Khi người click vào icon của collapsed layout, việc không hiển thị của collapsed layout sẽ thay đổi thành View.GONE và mở rộng sẽ trở nên nhìn thấy được như dưới đây:

FloatingViewService.java
case MotionEvent.ACTION_UP:
   int Xdiff = (int) (event.getRawX() - initialTouchX);
   int Ydiff = (int) (event.getRawY() - initialTouchY);
 
 
   //The check for Xdiff <10 && YDiff< 10 because sometime elements moves a little while clicking.
   //So that is click event.
   if (Xdiff < 10 && Ydiff < 10) {
       if (isViewCollapsed()) {
           //When user clicks on the image view of the collapsed layout,
           //visibility of the collapsed layout will be changed to "View.GONE"
           //and expanded view will become visible.
           collapsedView.setVisibility(View.GONE);
           expandedView.setVisibility(View.VISIBLE);
       }
   }
   return true;

Cuối cùng FloatingViewService.java sẽ như dưới đây:

FloatingViewService.java
import android.app.Service;
import android.content.Intent;
import android.graphics.PixelFormat;
import android.opengl.Visibility;
import android.os.IBinder;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.WindowManager;
import android.widget.ImageView;
import android.widget.Toast;
 
 
public class FloatingViewService extends Service {
   private WindowManager mWindowManager;
   private View mFloatingView;
 
   public FloatingViewService() {
   }
 
   @Override
   public IBinder onBind(Intent intent) {
       return null;
   }
 
   @Override
   public void onCreate() {
       super.onCreate();
       //Inflate the floating view layout we created
       mFloatingView = LayoutInflater.from(this).inflate(R.layout.layout_floating_widget, null);
 
       //Add the view to the window.
       final WindowManager.LayoutParams params = new WindowManager.LayoutParams(
               WindowManager.LayoutParams.WRAP_CONTENT,
               WindowManager.LayoutParams.WRAP_CONTENT,
               WindowManager.LayoutParams.TYPE_PHONE,
               WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
               PixelFormat.TRANSLUCENT);
 
       //Specify the view position
       params.gravity = Gravity.TOP | Gravity.LEFT;        //Initially view will be added to top-left corner
       params.x = 0;
       params.y = 100;
 
       //Add the view to the window
       mWindowManager = (WindowManager) getSystemService(WINDOW_SERVICE);
       mWindowManager.addView(mFloatingView, params);
 
       //The root element of the collapsed view layout
       final View collapsedView = mFloatingView.findViewById(R.id.collapse_view);
       //The root element of the expanded view layout
       final View expandedView = mFloatingView.findViewById(R.id.expanded_container);
 
       //Set the close button
       ImageView closeButtonCollapsed = (ImageView) mFloatingView.findViewById(R.id.close_btn);
       closeButtonCollapsed.setOnClickListener(new View.OnClickListener() {
           @Override
           public void onClick(View view) {
               //close the service and remove the from from the window
               stopSelf();
           }
       });
 
       //Set the view while floating view is expanded.
       //Set the play button.
       ImageView playButton = (ImageView) mFloatingView.findViewById(R.id.play_btn);
       playButton.setOnClickListener(new View.OnClickListener() {
           @Override
           public void onClick(View v) {
               Toast.makeText(FloatingViewService.this, "Playing the song.", Toast.LENGTH_LONG).show();
           }
       });
 
 
       //Set the next button.
       ImageView nextButton = (ImageView) mFloatingView.findViewById(R.id.next_btn);
       nextButton.setOnClickListener(new View.OnClickListener() {
           @Override
           public void onClick(View v) {
               Toast.makeText(FloatingViewService.this, "Playing next song.", Toast.LENGTH_LONG).show();
           }
       });
 
 
       //Set the pause button.
       ImageView prevButton = (ImageView) mFloatingView.findViewById(R.id.prev_btn);
       prevButton.setOnClickListener(new View.OnClickListener() {
           @Override
           public void onClick(View v) {
               Toast.makeText(FloatingViewService.this, "Playing previous song.", Toast.LENGTH_LONG).show();
           }
       });
 
 
       //Set the close button
       ImageView closeButton = (ImageView) mFloatingView.findViewById(R.id.close_button);
       closeButton.setOnClickListener(new View.OnClickListener() {
           @Override
           public void onClick(View view) {
               collapsedView.setVisibility(View.VISIBLE);
               expandedView.setVisibility(View.GONE);
           }
       });
 
 
       //Open the application on thi button click
       ImageView openButton = (ImageView) mFloatingView.findViewById(R.id.open_button);
       openButton.setOnClickListener(new View.OnClickListener() {
           @Override
           public void onClick(View view) {
               //Open the application  click.
               Intent intent = new Intent(FloatingViewService.this, MainActivity.class);
               intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
               startActivity(intent);
 
 
               //close the service and remove view from the view hierarchy
               stopSelf();
           }
       });
 
       //Drag and move floating view using user's touch action.
       mFloatingView.findViewById(R.id.root_container).setOnTouchListener(new View.OnTouchListener() {
           private int initialX;
           private int initialY;
           private float initialTouchX;
           private float initialTouchY;
 
 
           @Override
           public boolean onTouch(View v, MotionEvent event) {
               switch (event.getAction()) {
                   case MotionEvent.ACTION_DOWN:
 
                       //remember the initial position.
                       initialX = params.x;
                       initialY = params.y;
 
                       //get the touch location
                       initialTouchX = event.getRawX();
                       initialTouchY = event.getRawY();
                       return true;
                   case MotionEvent.ACTION_UP:
                       int Xdiff = (int) (event.getRawX() - initialTouchX);
                       int Ydiff = (int) (event.getRawY() - initialTouchY);
 
 
                       //The check for Xdiff <10 && YDiff< 10 because sometime elements moves a little while clicking.
                       //So that is click event.
                       if (Xdiff < 10 && Ydiff < 10) {
                           if (isViewCollapsed()) {
                               //When user clicks on the image view of the collapsed layout,
                               //visibility of the collapsed layout will be changed to "View.GONE"
                               //and expanded view will become visible.
                               collapsedView.setVisibility(View.GONE);
                               expandedView.setVisibility(View.VISIBLE);
                           }
                       }
                       return true;
                   case MotionEvent.ACTION_MOVE:
                       //Calculate the X and Y coordinates of the view.
                       params.x = initialX + (int) (event.getRawX() - initialTouchX);
                       params.y = initialY + (int) (event.getRawY() - initialTouchY);
 
 
                       //Update the layout with new X & Y coordinate
                       mWindowManager.updateViewLayout(mFloatingView, params);
                       return true;
               }
               return false;
           }
       });
   }
 
 
   /**
    * Detect if the floating view is collapsed or expanded.
    *
    * @return true if the floating view is collapsed.
    */
   private boolean isViewCollapsed() {
       return mFloatingView == null || mFloatingView.findViewById(R.id.collapse_view).getVisibility() == View.VISIBLE;
   }
 
 
   @Override
   public void onDestroy() {
       super.onDestroy();
       if (mFloatingView != null) mWindowManager.removeView(mFloatingView);
   }
}

2.3. Xử lý Overdraw permission

Bước cuối cùng còn lại là thêm floating view bằng cách bắt đầu FloatingViewService. Vì vậy, chúng ta cần phải kiểm tra xem các ứng dụng có quyền android.permission.SYSTEM_ALERT_WINDOW phép hay không? Đối với phiên bản Android <= API22, quyền này được cấp theo mặc định. Nhưng đối với các phiên bản Android chạy API> 22, chúng ta cần phải kiểm tra quyền runtime

Nếu quyền không được kích hoạt, chúng tôi sẽ mở màn hình quản lý cho quyền để cho phép người sử dụng để cấp phép sử dụng Settings.ACTION_MANAGE_OVERLAY_PERMISSION. Nó này sẽ mở ra bên dưới màn hình thuận lợi cho người sử dụng để cấp quyền android.permission.SYSTEM_ALERT_WINDOW.

Dưới đây là đoạn mã cho MainActivity đó sẽ hiển thị các floating view khi nút được click bằng cách kiểm tra sự cho phép SYSTEM_ALERT_WINDOW.

MainActivity.java
import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.provider.Settings;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.Toast;
 
 
public class MainActivity extends AppCompatActivity {
   private static final int CODE_DRAW_OVER_OTHER_APP_PERMISSION = 2084;
 
   @Override
   protected void onCreate(Bundle savedInstanceState) {
       super.onCreate(savedInstanceState);
       setContentView(R.layout.activity_main);
 
       //Check if the application has draw over other apps permission or not?
       //This permission is by default available for API<23. But for API > 23
       //you have to ask for the permission in runtime.
       if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !Settings.canDrawOverlays(this)) {
 
 
           //If the draw over permission is not available open the settings screen
           //to grant the permission.
           Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
                   Uri.parse("package:" + getPackageName()));
           startActivityForResult(intent, CODE_DRAW_OVER_OTHER_APP_PERMISSION);
       } else {
           initializeView();
       }
   }
 
   /**
    * Set and initialize the view elements.
    */
   private void initializeView() {
       findViewById(R.id.notify_me).setOnClickListener(new View.OnClickListener() {
           @Override
           public void onClick(View view) {
               startService(new Intent(MainActivity.this, FloatingViewService.class));
               finish();
           }
       });
   }
 
   @Override
   protected void onActivityResult(int requestCode, int resultCode, Intent data) {
       if (requestCode == CODE_DRAW_OVER_OTHER_APP_PERMISSION) {
           //Check if the permission is granted or not.
           if (resultCode == RESULT_OK) {
               initializeView();
           } else { //Permission is not available
               Toast.makeText(this,
                       "Draw over other app permission not available. Closing the application",
                       Toast.LENGTH_SHORT).show();
 
               finish();
           }
       } else {
           super.onActivityResult(requestCode, resultCode, data);
       }
   }
}

Bây giờ build and run project để xem kết quả

Source: http://www.androidhive.info/2016/11/android-floating-widget-like-facebook-chat-head/


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí