728x90
아래의 파일은 제가 파스칼 언어로 제작한 플로팅 위젯 입니다
기존에는 자바로 코딩하여 플로팅위젯을 이용하였지만 불편한 사람을 위해 파스칼로 다시 짰습니다.
꼭 이용하실 경우에는 권한중 다른앱 그리기 허용해주셔야 합니다.
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
자바코드 ↓
package com.KMSLyric;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.widget.Toast;
import android.app.Service;
import android.content.res.Configuration;
import android.graphics.Color;
import android.graphics.PixelFormat;
import android.os.IBinder;
import android.util.DisplayMetrics;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnTouchListener;
import android.view.WindowManager;
import android.widget.SeekBar;
import android.widget.SeekBar.OnSeekBarChangeListener;
import android.widget.TextView;
public class KMSLyricService extends Service {
private TextView mPopupView; //항상 보이게 할 뷰
private WindowManager.LayoutParams mParams; //layout params 객체. 뷰의 위치 및 크기를 지정하는 객체
private WindowManager mWindowManager; //윈도우 매니저
private SeekBar mSeekBar; //투명도 조절 seek bar
public static final String KMS_Lyric = "KMS_Lyric";
Intent KMSIntent = new Intent("com.KMSLyric.KMSLyricService");
String KMSMessage;
private float START_X,
START_Y; //움직이기 위해 터치한 시작 점
private int PREV_X,
PREV_Y; //움직이기 이전에 뷰가 위치한 점
private int MAX_X = -1,
MAX_Y = -1; //뷰의 위치 최대 값
private OnTouchListener mViewTouchListener = new OnTouchListener() {
@Override public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN: //사용자 터치 다운이면
if (MAX_X == -1)
setMaxPosition();
START_X = event.getRawX(); //터치 시작 점
START_Y = event.getRawY(); //터치 시작 점
PREV_X = mParams.x; //뷰의 시작 점
PREV_Y = mParams.y; //뷰의 시작 점
break;
case MotionEvent.ACTION_MOVE:
int x = (int)(event.getRawX() - START_X); //이동한 거리
int y = (int)(event.getRawY() - START_Y); //이동한 거리
//터치해서 이동한 만큼 이동 시킨다
mParams.x = PREV_X + x;
mParams.y = PREV_Y + y;
optimizePosition(); //뷰의 위치 최적화
mWindowManager.updateViewLayout(mPopupView, mParams); //뷰 업데이트
break;
}
return true;
}
};
@Override
public IBinder onBind(Intent arg0) {
return null;
}
@Override
public void onTaskRemoved(Intent rootIntent) { //핸들링 하는 부분
Toast.makeText(this, "KMSPack 강제종료 감지", Toast.LENGTH_SHORT).show();
stopSelf(); //서비스 종료
}
private BroadcastReceiver mMessageReceiver = new BroadcastReceiver() {
@Override public void onReceive(Context context, Intent intent) {
// TODO Auto-generated method stub
// Get extra data included in the Intent
if(intent.getAction().equals("com.KMSLyric.KMSLyricService")){
KMSMessage = intent.getStringExtra("KMS_Lyric");
mPopupView.setText(KMSMessage);
mWindowManager.updateViewLayout(mPopupView, mParams);
}
}
};
@Override
public void onCreate() {
super.onCreate();
KMSIntent.putExtra("KMS_Lyric","KMSPack 플로팅 가사창 입니다.");
sendBroadcast(KMSIntent);
final IntentFilter theFilter = new IntentFilter();
theFilter.addAction("com.KMSLyric.KMSLyricService");
registerReceiver(mMessageReceiver, theFilter);
mPopupView = new TextView(this); //뷰 생성
mPopupView.setText(KMSIntent.getStringExtra("KMS_Lyric")); //텍스트 설정
mPopupView.setTextSize(TypedValue.COMPLEX_UNIT_SP, 14); //텍스트 크기 18sp
mPopupView.setTextColor(Color.GREEN); //글자 색상
mPopupView.setBackgroundColor(Color.GRAY); //텍스트뷰 배경 색
//mPopupView.setBackgroundColor(Color.argb(127, 0, 255, 255)); //텍스트뷰 배경 색
mPopupView.setOnTouchListener(mViewTouchListener); //팝업뷰에 터치 리스너 등록
//최상위 윈도우에 넣기 위한 설정
mParams = new WindowManager.LayoutParams(
WindowManager.LayoutParams.WRAP_CONTENT,
WindowManager.LayoutParams.WRAP_CONTENT,
WindowManager.LayoutParams.TYPE_PHONE, //항상 최 상위에 있게. status bar 밑에 있음. 터치 이벤트 받을 수 있음.
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, //이 속성을 안주면 터치 & 키 이벤트도 먹게 된다.
//포커스를 안줘서 자기 영역 밖터치는 인식 안하고 키이벤트를 사용하지 않게 설정
PixelFormat.TRANSLUCENT); //투명
mParams.gravity = Gravity.LEFT | Gravity.TOP; //왼쪽 상단에 위치하게 함.
mWindowManager = (WindowManager)getSystemService(WINDOW_SERVICE); //윈도우 매니저 불러옴.
mWindowManager.addView(mPopupView, mParams); //최상위 윈도우에 뷰 넣기. *중요 : 여기에 permission을 미리 설정해 두어야 한다. 매니페스트에
addOpacityController(); //팝업 뷰의 투명도 조절하는 컨트롤러 추가
}
/*
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
// 서비스가 호출될 때마다 실행
return super.onStartCommand(intent, flags, startId);
}
*/
/**
* 뷰의 위치가 화면 안에 있게 최대값을 설정한다
*/
private void setMaxPosition() {
DisplayMetrics matrix = new DisplayMetrics();
mWindowManager.getDefaultDisplay().getMetrics(matrix); //화면 정보를 가져와서
MAX_X = matrix.widthPixels - mPopupView.getWidth(); //x 최대값 설정
MAX_Y = matrix.heightPixels - mPopupView.getHeight(); //y 최대값 설정
}
/**
* 뷰의 위치가 화면 안에 있게 하기 위해서 검사하고 수정한다.
*/
private void optimizePosition() {
//최대값 넘어가지 않게 설정
if (mParams.x > MAX_X)
mParams.x = MAX_X;
if (mParams.y > MAX_Y)
mParams.y = MAX_Y;
if (mParams.x < 0)
mParams.x = 0;
if (mParams.y < 0)
mParams.y = 0;
}
/**
* 알파값 조절하는 컨트롤러를 추가한다
*/
private void addOpacityController() {
mSeekBar = new SeekBar(this); //투명도 조절 seek bar
mSeekBar.setMax(100); //맥스 값 설정.
mSeekBar.setProgress(100); //현재 투명도 설정. 100:불투명, 0은 완전 투명
mSeekBar.setOnSeekBarChangeListener(new OnSeekBarChangeListener() {
@Override public void onStopTrackingTouch(SeekBar seekBar) {}
@Override public void onStartTrackingTouch(SeekBar seekBar) {}
@Override public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
mParams.alpha = progress / 100.0f; //알파값 설정
mWindowManager.updateViewLayout(mPopupView, mParams); //팝업 뷰 업데이트
}
});
//최상위 윈도우에 넣기 위한 설정
WindowManager.LayoutParams params = new WindowManager.LayoutParams(
WindowManager.LayoutParams.MATCH_PARENT,
WindowManager.LayoutParams.WRAP_CONTENT,
WindowManager.LayoutParams.TYPE_PHONE, //항상 최 상위에 있게. status bar 밑에 있음. 터치 이벤트 받을 수 있음.
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, //이 속성을 안주면 터치 & 키 이벤트도 먹게 된다.
//포커스를 안줘서 자기 영역 밖터치는 인식 안하고 키이벤트를 사용하지 않게 설정
PixelFormat.TRANSLUCENT); //투명
params.gravity = Gravity.LEFT | Gravity.TOP; //왼쪽 상단에 위치하게 함.
mWindowManager.addView(mSeekBar, params);
}
/**
* 가로 / 세로 모드 변경 시 최대값 다시 설정해 주어야 함.
*/
@Override
public void onConfigurationChanged(Configuration newConfig) {
setMaxPosition(); //최대값 다시 설정
optimizePosition(); //뷰 위치 최적화
}
@Override
public void onDestroy() {
if (mWindowManager != null) { //서비스 종료시 뷰 제거. *중요 : 뷰를 꼭 제거 해야함.
if (mPopupView != null)
mWindowManager.removeView(mPopupView);
if (mSeekBar != null)
mWindowManager.removeView(mSeekBar);
}
super.onDestroy();
}
}
파스칼 코드↓
unit KMSFloatingWidget;
///////////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////////////
// 네이버 카페 : https://cafe.naver.com/soyeoncode ////////////////////////////////////////////////////
// 코딩 작성자 : kimsyo5140@naver.com ////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////////////
interface
uses
Androidapi.JNI.Support, Androidapi.Helpers,
System.Threading, Androidapi.JNI.Widget, Androidapi.JNI.Util, Androidapi.JNI.GraphicsContentViewText,
Androidapi.JNIBridge, Androidapi.JNI.JavaTypes;
procedure FloatingWidgetCreate;
procedure FloatingonDestroy;
procedure setMaxPosition;
procedure optimizePosition; //최대값 넘어가지 않게 설정
type
KMSTouchEvent = class(TJavaLocal, JView_OnTouchListener)
private
public
constructor Create;
function onTouch(v: JView; event: JMotionEvent): Boolean; cdecl;
end;
var mViewTouchListener : KMSTouchEvent;
START_X, START_Y : Single; //움직이기 위한 터치 시작점
PREV_X, PREV_Y : Integer; //움직이기 이전에 뷰가 위치한 점
MAX_X, MAX_Y : Integer; //뷰의 위치 최대 값
WindowManagerObject : JObject;
mWindowManager : JWindowManager;
mParams : JWindowManager_LayoutParams;
mPopupView : JTextView;
implementation
procedure setMaxPosition;
var matrix : JDisplayMetrics;
begin
matrix := TJDisplayMetrics.JavaClass.init;
mWindowManager.getDefaultDisplay.getMetrics(matrix);
MAX_X := matrix.widthPixels - mPopupView.getWidth; //x 최대값 설정
MAX_Y := matrix.heightPixels - mPopupView.getHeight; //y 최대값 설정
end;
procedure optimizePosition; //최대값 넘어가지 않게 설정
begin
if (mParams.x > MAX_X) then
mParams.x := MAX_X;
if (mParams.y > MAX_Y) then
mParams.y := MAX_Y;
if (mParams.x < 0) then
mParams.x := 0;
if (mParams.y < 0) then
mParams.y := 0;
end;
constructor KMSTouchEvent.Create;
begin
inherited Create;
end;
function KMSTouchEvent.onTouch(v: JView; event: JMotionEvent): Boolean; cdecl;
begin
if event.getAction = TJMotionEvent.JavaClass.ACTION_DOWN then
begin
if MAX_X = -1 then
setMaxPosition;
START_X := event.getRawX(); //터치 시작 점
START_Y := event.getRawY(); //터치 시작 점
PREV_X := mParams.x; //뷰의 시작 점
PREV_Y := mParams.y; //뷰의 시작 점
end;
if event.getAction = TJMotionEvent.JavaClass.ACTION_MOVE then
begin
var x : integer := Trunc(event.getRawX() - START_X); //이동한 거리
var y : integer := Trunc(event.getRawY() - START_Y); //이동한 거리
//터치해서 이동한 만큼 이동 시킨다
mParams.x := PREV_X + x;
mParams.y := PREV_Y + y;
optimizePosition; //뷰의 위치 최적화
mWindowManager.updateViewLayout(mPopupView, mParams); //뷰 업데이트
end;
end;
procedure FloatingWidgetCreate;
begin
mViewTouchListener := KMSTouchEvent.Create;
MAX_X := -1;
MAX_Y := MAX_X;
mPopupView := TJTextView.JavaClass.init(SharedActivityContext); //뷰 생성
mPopupView.setText(StrToJCharSequence('수질 위젯' + #13#10 + '(수신대기)')); //텍스트 설정
mPopupView.setTextSize(TJTypedValue.JavaClass.COMPLEX_UNIT_SP, 14); //텍스트 크기 18sp
mPopupView.setTextColor(TJColor.JavaClass.GREEN); //글자 색상
mPopupView.setBackgroundColor(TJColor.JavaClass.GRAY); //텍스트뷰 배경 색
mPopupView.setOnTouchListener(mViewTouchListener);
mParams := TJWindowManager_LayoutParams.JavaClass.init(
TJWindowManager_LayoutParams.JavaClass.WRAP_CONTENT,
TJWindowManager_LayoutParams.JavaClass.WRAP_CONTENT,
TJWindowManager_LayoutParams.JavaClass.TYPE_APPLICATION_OVERLAY, //항상 최 상위에 있게. status bar 밑에 있음. 터치 이벤트 받을 수 있음.
TJWindowManager_LayoutParams.JavaClass.FLAG_NOT_FOCUSABLE, //이 속성을 안주면 터치 & 키 이벤트도 먹게 된다.
TJPixelFormat.JavaClass.TRANSLUCENT); //TRANSLUCENT = 투명
mParams.gravity := TJGravity.JavaClass.LEFT or TJGravity.JavaClass.TOP; //왼쪽 상단에 위치하게 함.
WindowManagerObject := SharedActivityContext.getSystemService(TJContext.JavaClass.WINDOW_SERVICE);
mWindowManager := TJWindowManager.Wrap((WindowManagerObject as ILocalObject).GetObjectID);
mWindowManager.addView(mPopupView, mParams);
end;
procedure FloatingonDestroy;
begin
if (mWindowManager <> nil) then
begin
if (mPopupView <> nil) then
mWindowManager.removeView(mPopupView);
end;
end;
end.
1. 액티비티에 이용하실 경우 ↓↓↓↓↓↓↓↓↓↓↓
uses문에 KMSFloatingWidget 추가 해주시고
예시로 아래처럼 사용하시면 됩니다
uses KMSFloatingWidget;
procedure TForm1.Button1Click(Sender: TObject);
begin
FloatingWidgetCreate// 플로팅 위젯 생성 표시
end;
procedure TextViewSetText(Tmpstr : string);
begin
mPopupView.setText(StrToJCharSequence(Tmpstr)); //텍스트뷰의 내용 설정
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
FloatingonDestroy; //플로팅 위젯 종료
end;
파이어몽키 안드로이드 서비스에 플로팅 위젯 서비스를 이용하실 경우
우선은 서비스를 포그라운드 서비스를 구현 하셔야 합니다 포그라운드 서비스 구현 안하고 이용하시는 방법은
매니페스트의 파일에서 SDK버전을 25로 변경해주시면 플로팅 위젯은 이용은 가능하나
반대로 서비스쪽은 불안정 할수가 있습니다
2. 일반적인 서비스에서 구동하는 방법입니다 ↓↓↓↓↓↓↓↓↓↓↓
//TDM의 폼에 이벤트 항목에 AndroidServiceConfigurationChanged 더블클릭하면 아래처럼 코드 나오며
//setMaxPosition 와 optimizePosition를 넣어주시면 됩니다.
uses KMSFloatingWidget;
procedure TextViewSetText(Tmpstr : string);
begin
mPopupView.setText(StrToJCharSequence(Tmpstr)); //텍스트뷰의 내용 설정
end;
function TDM.AndroidServiceStartCommand(const Sender: TObject;
const Intent: JIntent; Flags, StartId: Integer): Integer;
begin
FloatingWidgetCreate;
end;
procedure TDM.AndroidServiceConfigurationChanged(const Sender: TObject;
const NewConfig: JConfiguration);
begin
setMaxPosition();
optimizePosition();
end;
3. 서비스 쓰레드 사용하실 경우 ↓↓↓↓↓↓↓↓↓↓↓
서비스 내에 쓰레드 사용하시면 플로팅 텍스트 내용을 변경하실 경우에는 예시로 아래처럼 이용해주셔야 합니다
서비스 쓰레드 안사용하실경우 아래처럼 사용하실 필요가 없습니다.
쓰레드내에 아래처럼 처리 안하실경우 서비스 멈춤현상 일어나게 되며 정상적인 서비스가 이용이 안되니
꼭 쓰레드내에 mPopupView.setText사용하실 경우 아래처럼 해주셔야합니다
uses KMSFloatingWidget;
JTextRunnable = class(TJavaLocal, JRunnable)
private
TextTmpStr : string;
public
constructor Create;
procedure run; cdecl;
end;
var TextRunnable : JTextRunnable;
TextHandle : JHandler;
constructor JTextRunnable.Create;
begin
inherited Create;
end;
procedure JTextRunnable.run;
begin
mPopupView.setText(StrToJCharSequence(TextTmpStr));
end;
procedure ThreadSetTextView;
begin
TextHandle.post(TextRunnable);
end;
function TDM.AndroidServiceStartCommand(const Sender: TObject;
const Intent: JIntent; Flags, StartId: Integer): Integer;
begin
TextHandle := TJHandler.JavaClass.init(TJLooper.JavaClass.getMainLooper); // 텍스트 뷰 핸들 생성
TextRunnable := JTextRunnable.Create; // 텍스트뷰 Runnable 생성
FloatingWidgetCreate;
TTask.Run(procedure
begin
while True do
begin
sleep(300000); //300000
TextRunnable.TextTmpStr := 'ㅇㄹㅇㄹ';
ThreadSetTextView;
Exit;
end;
end).Start;
end;
procedure TDM.AndroidServiceDestroy(Sender: TObject);
begin
FloatingonDestroy;
end;
728x90
'FireMonkey 예제 자료' 카테고리의 다른 글
FireMonkey(파이어몽키) Live2D_android.jar sdk 활용한 live2d 실행 (0) | 2023.01.07 |
---|---|
FireMonkey 갤럭시워치4(Wear OS) Edit 내용 작성하기 위해 클릭시 에러 뜨는 현상(임시 조치) (0) | 2022.05.14 |
FireMonkey 안드로이드 서비스 미디어 볼륨 최대 올려주기 (0) | 2022.04.02 |
FireMonkey 안드로이드 타 쓰레드 Toast 알림 사용하기(액티비티,서비스) 가능 (0) | 2022.04.01 |
FireMonkey 안드로이드 화면 켜짐 유지 (0) | 2022.03.20 |
파이어몽키(firemonkey) 코드로 화면 (세로,가로)전환 (0) | 2022.03.14 |
파이어몽키 안드로이드 EUC-KR Encode 인코딩 (0) | 2022.01.18 |
파이어몽키 안드로이드 지정된 폴더에 파일 목록 불러오기 (0) | 2022.01.18 |
댓글