android Floating Widget 구현을 자바로 알아보자.
우선 mainfests -> AndroidManifest.xml에서 앱을 다른 모든 앱 위에 표시할 수 있게 권한을 설정하고
Service를 시작하도록 호출하고 enabled를 true로 하여 활성화시킨다.
코드는 다음과 같다.
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
<service android:name=".WidgetService"
android:enabled="true"/>
다음은 activity.main.xml로 가서 버튼을 간단하게 하나 만들어준다.
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<Button
android:id="@+id/notify_me"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Button"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
MainActivity에는 권한 설정과 버튼을 눌렀을 때 이벤트를 만든다.
주석을 보면서 코드 분석을 해보면 좋을 것 같다.
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.provider.Settings;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 버튼 정의
Button button;
button = (Button) findViewById(R.id.notify_me);
// 권한을 확인한다.
getpermission();
// 버튼 클릭
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if (!Settings.canDrawOverlays(MainActivity.this)){
getpermission();
}else {
// 권한이 설정돼있으면 위젯 액티비티로 연결
Intent intent = new Intent(MainActivity.this, WidgetService.class);
startService(intent);
// 액티비티에 연결 후 MainActivity 종료
finish();
}
}
});
}
// M 버전(안드로이드 6.0 마시멜로우 버전) 보다 같거나 큰 API에서만 설정창 이동 가능
public void getpermission(){
// 지금 창이 오버레이 설정창이 아니라면
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !Settings.canDrawOverlays(this)){
Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:"+getPackageName()));
startActivityForResult(intent,1);
}
}
// WidgetService 에서 처리된 결과를 받는 메소드
// 처리된 결과 코드가 requestCode 를 판별해 결과 처리를 진행한다.
// WidgetService 에서 처리 결과가 담겨온 데이터를 메시지로 보여준다.
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);
// 권한 여부 확인
if(requestCode == 1){
// 권한을 사용할 수없는 경우 알림 표시
if (!Settings.canDrawOverlays(MainActivity.this)){
Toast.makeText(this, "Permission denied by user", Toast.LENGTH_SHORT).show();
}
}
}
}
다음으로 새로운 activity를 하나 만들어준다.
여기서는 WidgetService라는 이름으로 activity를 하나 만들고 위젯 작동에 대한 이벤트를 만든다.
import android.app.Service;
import android.content.Intent;
import android.graphics.PixelFormat;
import android.os.Build;
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.TextView;
import androidx.annotation.Nullable;
import java.util.Calendar;
public class WidgetService extends Service {
int LAYOUT_FLAG;
View mFloatingView;
WindowManager windowManager;
ImageView imageClose;
TextView tvWidth;
float height, width;
@Nullable
@Override
public IBinder onBind(Intent intent){
return null;
}
@Override
public int onStartCommand(Intent intent, int flags, int startId){
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O){
LAYOUT_FLAG = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
}else{
LAYOUT_FLAG = WindowManager.LayoutParams.TYPE_PHONE;
}
// 우리가 만든 플로팅 뷰 레이아웃 확장
mFloatingView = LayoutInflater.from(this).inflate(R.layout.layout_widget, null);
WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams(WindowManager.LayoutParams.WRAP_CONTENT,
WindowManager.LayoutParams.WRAP_CONTENT,
LAYOUT_FLAG,
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
PixelFormat.TRANSLUCENT);
// 보기 위치 지정
// 처음에는보기가 오른쪽 상단 모서리에 추가되며 필요에 따라 x-y 좌표를 변경
layoutParams.gravity = Gravity.TOP|Gravity.RIGHT;
layoutParams.x = 0;
layoutParams.y = 100;
WindowManager.LayoutParams imageParams = new WindowManager.LayoutParams( 140,
140,
LAYOUT_FLAG,
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
PixelFormat.TRANSLUCENT);
imageParams.gravity = Gravity.BOTTOM|Gravity.CENTER;
imageParams.y = 100;
windowManager = (WindowManager)getSystemService(WINDOW_SERVICE);
imageClose = new ImageView(this);
imageClose.setImageResource(R.drawable.close);
imageClose.setVisibility(View.INVISIBLE);
windowManager.addView(imageClose, imageParams);
windowManager.addView(mFloatingView, layoutParams);
mFloatingView.setVisibility(View.VISIBLE);
height = windowManager.getDefaultDisplay().getHeight();
width = windowManager.getDefaultDisplay().getWidth();
tvWidth = (TextView) mFloatingView.findViewById(R.id.imageView);
// 사용자의 터치 동작을 사용하여 플로팅 뷰를 드래그하여 이동
tvWidth.setOnTouchListener(new View.OnTouchListener() {
int initialx, initialy;
float initialTouchX, initialTouchY;
long startCkickTime;
// 클릭으로 볼 최대시간
int MAX_CLICK_DURATION = 200;
@Override
public boolean onTouch(View view, MotionEvent motionEvent) {
switch (motionEvent.getAction()){
case MotionEvent.ACTION_DOWN:
startCkickTime = Calendar.getInstance().getTimeInMillis();
imageClose.setVisibility(View.VISIBLE);
// 초기 위치 기억
initialx = layoutParams.x;
initialy = layoutParams.y;
//터치 위치 좌표 얻기
initialTouchX = motionEvent.getRawX();
initialTouchY = motionEvent.getRawY();
return true;
case MotionEvent.ACTION_UP:
long clickDuration = Calendar.getInstance().getTimeInMillis()-startCkickTime;
imageClose.setVisibility(view.GONE);
// 초기 좌표와 현재 좌표의 차이 가져 오기
layoutParams.x = initialx+(int) (initialTouchX-motionEvent.getRawX());
layoutParams.y = initialy+(int)(motionEvent.getRawY()-initialTouchY);
// 사용자가 플로팅 위젯을 제거 이미지로 끌어다 놓으면 서비스를 중지합니다.
if(clickDuration>=MAX_CLICK_DURATION)
{
// 제거 이미지 주변 거리
if (layoutParams.y>(height * 0.6 )){
stopSelf();
}
}
return true;
case MotionEvent.ACTION_MOVE:
// 초기 좌표와 현재 좌표의 차이 가져 오기
layoutParams.x = initialx+(int)(initialTouchX-motionEvent.getRawX());
layoutParams.y = initialy +(int) (motionEvent.getRawY()- initialTouchY);
// 새로운 X 및 Y 좌표로 레이아웃 업데이트
windowManager.updateViewLayout(mFloatingView, layoutParams);
if (layoutParams.y> (height * 0.6)){
imageClose.setImageResource(R.drawable.close);
}
else {
imageClose.setImageResource(R.drawable.close);
}
return true;
}
return false;
}
});
return START_STICKY;
}
// 앱이 종료될때 실행
@Override
public void onDestroy(){
super.onDestroy();
if(mFloatingView != null){
windowManager.removeView(mFloatingView);
}
if (imageClose != null){
windowManager.removeView(imageClose);
}
}
}
여기서 이미지는 자신이 원하는 이미지로 쓰면 된다.
다음으로 위젯의 형태를 만들어보자.
새로운 레이아웃을 만들자. 여기서는 layout_widget.xml이라는 이름으로 만들었다.
코드는 다음과 같다.
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<RelativeLayout
android:id="@+id/layout_widget"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_margin="30dp">
<TextView
android:id="@+id/imageView"
android:layout_width="60dp"
android:layout_height="60dp"
android:gravity="center"
android:textColor="#ffffff"
android:background="@drawable/pin"/>
</RelativeLayout>
</RelativeLayout>
위 내용을 잘 따라왔다면 run을 눌러 잘 실행되는지 확인해본다.
위 코드를 실행하게 되면 다음 출력 화면과 같은 결과를 얻을 수 있다.
+ 카카오톡 위젯을 확인해보면 위젯을 이동하고 놓았을 때 제일 가까운 사이드로 바로 붙어버리는데 나도 다시 공부한 후 양 사이드로 바로 붙어버리게 구현해봐야 할 것 같다. 그리고 클릭했을 때 앱의 정보가 나오거나 앱 홈 화면으로 다시 돌아가게도 구현하고 싶다.
참고
github
'Develop > Java' 카테고리의 다른 글
[AWS] 아마존 웹 서비스 Android + Amazon Cognito 로그인 구현 (0) | 2021.07.15 |
---|---|
[AWS] 아마존 웹 서비스 Android + Amazon Cognito 구현 (0) | 2021.07.14 |
[AWS] 아마존 웹 서비스 Amplify + Android 프로젝트 연동 (0) | 2021.07.12 |
[Java] 자바 android Floating Widget 구현(3) (1) | 2021.07.12 |
[Java] 자바 android Floating Widget 구현(2) (0) | 2021.07.08 |