다른 앱 위에 그리기
Service 를 이용하여 최상위 화면에 항상 이미지를 그리는 방법을 알아보자.
AndroidManifest 파일을 설정하자.
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<service
android:name=".AwindowService"
android:enabled="true"
android:permission="android.permission.SYSTEM_ALERT_WINDOW">
</service>
다른 앱 위에 그리기 권한과
Android O 이상에서 서비스를 실행하기 위한 Foreground Service 권한을 설정한다.
public class MainActivity extends AppCompatActivity {
.................
public void checkPermission() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { // 마시멜로우 이상일 경우
if (!Settings.canDrawOverlays(this)) { // 다른앱 위에 그리기 체크
Uri uri = Uri.fromParts("package" , getPackageName(), null);
Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, uri);
startActivityForResult(intent, ACTION_MANAGE_OVERLAY_PERMISSION_REQUEST_CODE);
} else {
startMain();
}
} else {
startMain();
}
}
@TargetApi(Build.VERSION_CODES.M)
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == ACTION_MANAGE_OVERLAY_PERMISSION_REQUEST_CODE) {
if (!Settings.canDrawOverlays(this)) {
finish();
} else {
startMain();
}
}
}
void startMain(){
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
startForegroundService(new Intent(this, AwindowService.class));
} else {
startService(new Intent(this, AwindowService.class));
}
}
.................
다른 앱위에 그리기 권한을 사용자에게 요구하는 소스 코드이다.
권한 획득 후에 서비스를 실행한다.
Android O 이상에서는 ForeGround Service를 호출한다.
public class AwindowService extends Service {
...................
@Override
public IBinder onBind(Intent intent) { return null; }
@Override
public void onCreate() {
super.onCreate();
// Android O 이상일 경우 Foreground 서비스를 실행
// Notification channel 설정.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
final String strId = getString(R.string.noti_channel_id);
final String strTitle = getString(R.string.app_name);
NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
NotificationChannel channel = notificationManager.getNotificationChannel(strId);
if (channel == null) {
channel = new NotificationChannel(strId, strTitle, NotificationManager.IMPORTANCE_HIGH);
notificationManager.createNotificationChannel(channel);
}
Notification notification = new NotificationCompat.Builder(this, strId).build();
startForeground(1, notification);
}
LayoutInflater inflate = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
// inflater 를 사용하여 layout 을 가져오자
WindowManager wm = (WindowManager) getSystemService(WINDOW_SERVICE);
// 윈도우매니저 설정
WindowManager.LayoutParams params = new WindowManager.LayoutParams(
WindowManager.LayoutParams.WRAP_CONTENT,
WindowManager.LayoutParams.WRAP_CONTENT,
Build.VERSION.SDK_INT >= Build.VERSION_CODES.O?
WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY : WindowManager.LayoutParams.TYPE_SYSTEM_ALERT,
// Android O 이상인 경우 TYPE_APPLICATION_OVERLAY 로 설정
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
|WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL|WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH,
PixelFormat.TRANSLUCENT);
params.gravity = Gravity.LEFT|Gravity.CENTER_VERTICAL;
// 위치 지정
View mView = inflate.inflate(R.layout.view_in_service, null);
// view_in_service.xml layout 불러오기
// mView.setOnTouchListener(onTouchListener);
// Android O 이상의 버전에서는 터치리스너가 동작하지 않는다. ( TYPE_APPLICATION_OVERLAY 터치 미지원)
final ImageButton btn_img = (ImageButton) mView.findViewById(R.id.btn_img);
btn_img.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.d("test","onClick ");
// do something!
}
});
// btn_img 에 android:filterTouchesWhenObscured="true" 속성 추가하면 터치리스너가 동작한다.
btn_img.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View view, MotionEvent motionEvent) {
switch (motionEvent.getAction()){
case MotionEvent.ACTION_DOWN:
Log.d("test","touch DOWN ");
break;
case MotionEvent.ACTION_UP:
Log.d("test","touch UP");
break;
case MotionEvent.ACTION_MOVE:
Log.d("test","touch move ");
break;
}
return false;
}
});
wm.addView(mView, params); // 윈도우에 layout 을 추가 한다.
}
@Override
public void onDestroy() {
super.onDestroy();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
stopForeground(true); // Foreground service 종료
}
if(wm != null) {
if(mView != null) {
wm.removeView(mView); // View 초기화
mView = null;
}
wm = null;
}
}
}
Android 8.0 이상 부터는
ForeGround Service를 실행하기 위한 Notification channel 을 생성한다.
또한 TYPE_APPLICATION_OVERLAY 를 기본으로 설정하여야 한다.
* TYPE_APPLICATION_OVERLAY를 사용하면 터치 이벤트가 발생하지 않아 View의 위치이동이 불가하다. *
android:filterTouchesWhenObscured="true"
뷰에 위 속성 값을 추가하면 터치 이벤트가 발생하는 것을 확인 할수 있다.
<ImageButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/btn_img"
android:text="click"
android:filterTouchesWhenObscured="true"
android:textColor="#ffffff"
android:src="@drawable/arrow"
android:background="@null"
android:scaleType="centerCrop"/>
결과 화면
안드로이드 9.0 기기에서 테스트한 결과이다.
홈화면에서 지정된 이미지가 표시되며
또한 터치이벤트를 이용하여 사용자가 이미지를 드래그시 동작하는 것을 확인할 수 있다.
'프로그램개발' 카테고리의 다른 글
[Android] 화면 자동 회전 On/Off (0) | 2020.04.09 |
---|---|
[Android] RSS 뉴스, XML 파싱하기 (7) | 2020.03.18 |
[Android] Android Studio에서 기기 화면 캡쳐하기 (0) | 2020.03.17 |
[Android] ListView 새로고침/갱신하기 (with BaseAdapter) (0) | 2020.02.17 |