Dev./Android

like miller 2012. 10. 22. 14:33

항상 최상위에 나오는 뷰 만들기2 (팝업 비디오 + Q슬라이드)


이전에 쓴 글 '항상 최상위에 나오는 뷰 만들기'는 뷰는 나오지만 터치 이벤트를 받지 못했다. 터치 이벤트를 받더라도 ACTION_OUTSIDE 이벤트만 받을 수 있었다.


이제는 그냥 최상위 뷰만 나오게 하는 것이 아니라 뷰를 최상위에 나오게 하면서 모든 터치 이벤트를 받아보자. 터치로 뷰를 이동해보고(갤럭시의 팝업 비디오 처럼) 투명도를 조절해보자!!(옵티머스의 Q슬라이드)


이전에 쓴 '항상 최상위에 나오는 뷰 만들기' 와 방식은 같다.

1. 최상위에 나오게 하기 위해서는 Window에 뷰는 넣는다.

2. 다른 화면에서도 나오게 하기위해서는 서비스에서 뷰를 생성하여야 한다.

3. 뷰에 들어오는 터치 이벤트를 onTouchListener를 통해서 받는다.


1. 서비스 생성

자신의 앱이 종료된 후에도 항상 해당 뷰가 떠 있어야 한다. 그래서 Activity에서 뷰를 추가하는 것이 아니라 Service에서 뷰를 추가 해야 한다.


AlwaysOnTopService.java

public class AlwaysOnTopService extends Service {
    @Override
    public IBinder onBind(Intent arg0) { return null; }
   
    @Override
    public void onCreate() {
        super.onCreate();
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
    }
}


2. 뷰 생성 및 최상위 윈도우에 추가

간단하게 텍스트뷰 하나 추가하는 코드이다.

    private TextView mPopupView;                            //항상 보이게 할 뷰
    private WindowManager.LayoutParams mParams;  //layout params 객체. 뷰의 위치 및 크기
    private WindowManager mWindowManager;          //윈도우 매니저

    @Override
    public void onCreate() {
        super.onCreate();

        mPopupView = new TextView(this);                                         //뷰 생성
        mPopupView.setText("이 뷰는 항상 위에 있다.");                        //텍스트 설정
        mPopupView.setTextSize(TypedValue.COMPLEX_UNIT_SP, 18); //텍스트 크기 18sp
        mPopupView.setTextColor(Color.BLUE);                                  //글자 색상
        mPopupView.setBackgroundColor(Color.argb(127, 0, 255, 255)); //텍스트뷰 배경 색

        //최상위 윈도우에 넣기 위한 설정
        mParams = new WindowManager.LayoutParams(
            WindowManager.LayoutParams.WRAP_CONTENT,
            WindowManager.LayoutParams.WRAP_CONTENT,
            WindowManager.LayoutParams.TYPE_PHONE,//항상 최 상위. 터치 이벤트 받을 수 있음.
            WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,  //포커스를 가지지 않음
            PixelFormat.TRANSLUCENT);                                        //투명
        mParams.gravity = Gravity.LEFT | Gravity.TOP;                   //왼쪽 상단에 위치하게 함.
       
        mWindowManager = (WindowManager) getSystemService(WINDOW_SERVICE);  //윈도우 매니저
        mWindowManager.addView(mPopupView, mParams);      //윈도우에 뷰 넣기. permission 필요.
    }


 이전 글에서는 TYPE을 TYPE_SYSTEM_OVERLAY로 주었다. 이러면 화면 전체를 대상으로 뷰를 넣지만 터치 이벤트를 받지는 못한다.

 하지만 TYPE을 TYPE_PHONE으로 설정하면 터치 이벤트를 받을 수 있다. 하지만 Status bar 밑으로만 활용가능하고 뷰가 Focus를 가지고 있어 원래 의도대로 뷰 이외의 부분에 터치를 하면 다른 앱이 터치를 사용해야 하는데 이것이 불가능 하고 키 이벤트 까지 먹어 버린다.

 이것을 해결하기 위해 FLAG 값으로 FLAG_NOT_FOCUSABLE을 주면 뷰가 포커스를 가지지 않아 뷰 이외의 부분의 터치 이벤트와 키 이벤트를 먹지 않아서 자연스럽게 동작할 수 있게 된다.


3. 매니페스트에 퍼미션 설정

WinodwManager에 addView 메소드를 사용하려면 android.permission.SYSTEM_ALERT_WINDOW 퍼미션이 필요하다.


<manifest  ................ >
    <application ................ >
        <activity
           ................
        </activity>
        <service
           ................
        </service>
    </application>
    <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
    <uses-sdk android:minSdkVersion="7" />
</manifest>


이전 글에는 service 태그 안에 퍼미션을 주라고 했지만 service에 주지 않아도 된다. 그냥 uses-permission을 주면 된다.


4. 터치 이벤트 받기

뷰에 터치 리스너를 등록하면 터치 이벤트를 받을 수 있다.


mPopupView.setOnTouchListener(mViewTouchListener);              //팝업뷰에 터치 리스너 등록


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


터치로 뷰를 이동하거나 크기 조절을 하려면 WindowManager.LayoutParams 객체의 값을 변경해 주면 된다. x, y는 뷰의 시작점 좌표이다. Q슬라이드 처럼 투명도 조절은alpha값을 변경하면 된다. 0~1사의 값을 넣어 주면 된다.

이렇게 WindowManager.LayoutParams의 값을 변경해준 다음 WindowManager의 updateViewLayout메소드를 사용하여 뷰의 변경사항을 적용한다.



5. 뷰 제거

서비스 종료시 뷰를 제거 해야 한다.

    @Override
    public void onDestroy() {
        if(mWindowManager != null) {        //서비스 종료시 뷰 제거. *중요 : 뷰를 꼭 제거 해야함.
            if(mPopupView != null) mWindowManager.removeView(mPopupView);
            if(mSeekBar != null) mWindowManager.removeView(mSeekBar);
        }
        super.onDestroy();
    }


6. 서비스 실행/중지 할 activity 만들기

AlwaysOnTopActivity.java


public class AlwaysOnTopActivity extends Activity implements onClickListener {
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
       
        findViewById(R.id.start).setOnClickListener(this);        //시작버튼
        findViewById(R.id.end).setOnClickListener(this);            //중시버튼
    }
   
    @Override
    public void onClick(View v) {
        int view = v.getId();
        if(view == R.id.start)
            startService(new Intent(this, AlwaysOnTopService.class));    //서비스 시작
        else
            stopService(new Intent(this, AlwaysOnTopService.class));    //서비스 종료
    }
}


실행 결과


 앱 시작 뷰 추가  바탕화면 (위치 이동)

 동영상 재생
 Dragon Flight 게임
인터넷 (투명값 변경)




AlwaysOnTop.zip

전체 샘플 코드 첨부하였습니다.

*글과 자료는 출처만 밝히시면 얼마든지 가져다 쓰셔도 됩니다.


이전 댓글 더보기
안녕하세요 많이 배워갑니다.
질문 하나 드리고 싶은데요...
WindowManager.LayoutParams.TYPE_PHONE,//항상 최 상위. 터치 이벤트 받을 수 있음.
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, //포커스를 가지지 않음
로 설정되어있는 뷰를 다른 이벤트(버튼이벤트) 를 통해
FLAG_NOT_FOCUSABLE
이벤트 같은것으로 바꿔 적용시켜줄수 있나요?

최상위 뷰와 다른 뷰의 터치포커스 를 전환 시켜주고 싶어서 그렇습니다.
windowmanager 의 updateViewLayout 메소드를 통해서 해당 뷰의 설정을 변경해 줄 수 있습니다.
감사합니다. updateViewLayout으로는 Type 는 수정이 안되나봅니다..
java.lang.IllegalArgumentException: Window type can not be changed after the window is added.
에러가 계속뜨네요....
그러면 removeView 후 다시 addView 하는 방식으로 해야겠네요.
질문이 하나 있습니다.
간편하게 하려고
int x = (int)event.getRawX();
int y = (int)event.getRawY();
mParams_itme.x = x;
mParams_itme.y = y;
Log.d("Service", "Touch :"+x+"/"+y);
optimizePosition();
mWindowManager.updateViewLayout(main_item, mParams_itme);

로 바로 좌표를 버튼에 적용햇는데
가운데에 거울이 있듯이 적용이 되어버리네요,..;;
y좌표는 정상인데 x좌표만 그렇게..;;
거기다가 무슨이유인지 위의 뷰에 버튼을 좀더 달았더니
x좌표로 미동도 하지 않습니다...;;
아....3시간만에 해결햇습니다..ㅠㅠ
x값이 오른쪽 스크린을 기준으로 +가 되더라구요,,;;
bitmap할때랑 헷갈렷네...;;
안녕하세요 좋은 소스 잘보았습니다.

다름이 아니라 화면 최상단 Statusbar를 Full_Screen으로 완전 없애거나

Status클릭시 notification 이벤트를 막을수 있는방법이 있는지 문의 드립니다.
뷰 등록 시 TYPE_PHONE으로 하면 stausbar 까지 뷰를 올릴 수 있습니다.

투명 뷰를 statusbar 크기 만큼 올려서 터치를 막으면 statusbar를 막을 수 있습니다.
좋은 자료 감사합니다.

질문있습니다.

다른앱 클릭시 숨기고 싶은데 어떻게 해야할까요?
다른 앱 클릭 시 라는게 다른 앱이 실행되는 것을 의미하는건가요?

그렇다면 약간 힘듭니다. 다른 앱들이 실행되는 이벤트를 감지하는게 어렵습니다.

예전에는 로그를 읽어서 어떤 앱이 실행되는지 알 수 있었지만

현재는 자기 앱의 로그밖에 읽을 수 없기 때문입니다.
좋은 코드 감사합니다.
혹시 최상위뷰를 이용해서 게임 및 홈화면에 대한 전체화면을 캡쳐가 가능할까요?
화면캡쳐를 이런 방식으로는 안됩니다.

프레임 버퍼에 접근하는 방식으로 하셔야 합니다.
감사합니다. 혹시 최상위 뷰에서 생성한 마우스 커서가 있는데 그 마우스 커서로 다른 앱이라던가 바탕화면을 클릭 이벤트를 발생시킬 수 있는지 궁금하네요.
핸드폰에 강제 터치 이벤트를 발생하는게 루팅된 단말이거나 시스템 권한을 얻어야 하는데 일반 3rd party app 에서는 힘듭니다.
좋은 소스 감사해요!!
제가 정말 입문단계에 있는 초보자인데요
에디트텍스트를 넣은다음에 스타트를 눌렀을때
그 문구가 나오게끔 하고싶어요
약간 할일 적어놓듯이 화면에 계속 띄어놓게끔이요
근데 에디트텍스트를 넣은다음에 어떻게해야 나오는지 하나도 모르겠어요 ㅠㅠ
알려주시면 감사하겠습니다
인텐트문으로 쓰는것도 괜찮은건가요?
근데 자바 하나는 액티비티고 하나는 서비스라서
어떻게 하는질 모르는데 다른 쉬운방법 없을까요?
보고 잘 배웠습니다.
질문이 있습니다.
혹시 이미지뷰를 최상위 뷰로 만들어 논후
이 이미지가 동기화를 이용해서 좌표 바꾸는 걸로 움직이게 할 수 있나요 ??
또는 다른 방식으로 라도 터치가 아니라 스스로 움직이도록
이벤트는 타이머 같은 특정 시간을 이용해도 되구요.
브로드캐스트를 받아서 처리해도 되고
서버와 통신하여 처리해도 됩니다.

어떤 이벤트를 사용할지는 자신의 앱 특성에 달렸습니다. 뷰의 이동은 가능하니까요.
질문드립니다.. 최상위뷰 해놓고 텍스트뷰를 터치하면 스크린샷을 찍는걸 해고보고픈데.. 그 최상위일떄 화면을 어떻게 bitmap에 담을지를 잘모르겠어요..가르쳐주실수있으신가요??..
정말 매우 감사드립니다.
너무 감사드립니다
뷰를 클릭하면 클릭한 좌표를 얻어오되 그 뷰의 뒤쪽에 있는 컨텐츠에도 클릭이벤트가 그대로 가게끔 하려면 어떻게 해야하나요?

매크로 프로그램 같은걸 만들려고 합니다. 사용자가 입력을 하면 그 터치 이벤트들을 수집해다가 나중에 반복하게 해주려고 해서 그 좌표를 받으려고 서비스로 된 최상단 투명 뷰를 이용하려고 했는데 안되네요....
항상 최상위에 나오는 뷰 만들기1을 참고하시면 됩니다.

http://blog.daum.net/mailss/18
안녕하세요. 정말 덕분에 잘 배워갑니다.
하나 여쭤보고 싶은게 있는데요. 뷰를 터치했을 때,
setMaxPosition() 이 함수안에서
mWindowManager.getDefaultDisplay().getMetrics(matrix);
에서 NullPointerException 이 났는데요. 어떻게 해야하는거죠?

그전에 Exception dispatching input event.
Exception in Message Queue callback: handleReceiveCallback 도
log에 있었습니다. 확인 부탁드립니다.
제가 올린 소스는 샘플소스입니다.

샘플소스이기에 예외처리 부분은 미흡합니다.

그 소스를 이해하고 적용하는 것은 본인 몫입니다.

소스감사합니다. 많이배워갑니다~^^*
최상위 뷰를 무조건 세로로 출력 시킬수 있나요?
만약 가로 동영상을 보고 있다면 최상위 뷰도 가로로 전환되어 출력되는데
이걸 무조건 세로로 보여지게 할수 있나요??
뷰를 커스텀하게 만드셔야 합니다. onDraw를 상속받으셔서 canvas에서 회전을 시키면 됩니다.

아니면 뷰의 setRotation 메소드를 이용해 보셔도 됩니다
안녕하세요 질문좀 할게요~ ㅎㅎ
제가 지금 계산기어플을 만들어보려고 하는데
지금 텍스트까지는 플로팅 윈도우가 되는데
안에다가 텍스트 말고 계산기화면을 띄우고 싶어요
근데 서비스에서는 셋컨텐트뷰가 안돼서 메인 유아이를 띄울수가없어요
좋은 방법 없나요?ㅜㅠ
텍스트뷰를 출력하는 거랑 계산기 화면을 출력하는 거랑 같습니다.

결국은 뷰를 window에 넣는 것이니까요.

계산기 화면을 코드로 구성하여 root view를 넣으셔도 되고

xml로 구성후 LayoutInflater로 view를 생성후 넣으셔도 됩니다.
포스팅 잘 보았습니다.
도움이 많이 되네요.
한가지 질문이 있어 드립니다.
필요에 의해 LinearLayout을 FILL_PARENT 하여 전체 touch envet를 받고 있는데요. 현재 상태에서 뒤쪽에 있는 view까지 touch event를 통과시켜 동작할 수 있는 방법은 없나요?
즉. LinearLayout에서 터치이벤트도 받아야 하며, 뒤쪽 view에 그 터치이벤트를 전달해야 하는 상황입니다.
혹시 그방법 실현했나요?
저도 그거때문에 애먹고 있어요
해결했다면 도움 부탁드립니다
안녕하세요.
전화가 올때 통화앱위에 이 방법으로 뛰우고 있는데요.
한가지 질문이 있습니다.
잠금화면에 패턴이 있거나 비밀번호를 설정해 놓으면 통화앱위에 뜨지 않습니다.
뜨게 하려면 TYPE_SYSTEM_OVERLAY 를 설정해야 하는데 이건 터치리스너가 먹지 않네요.
이런경우 어떻게 해야 할까요?
저도 이부분에 막혀서 고민하고 있습니다. 해결책이 있을까요?
안드로이드 초보인데 도움이 많이 되었습니다.
그런데 한가지 질문이 있는데, 리스트뷰를 띄우고 싶은데,
Arrayadapter때문인지 어플이 꺼지면 리스트뷰가 없어집니다. 해결 방안이 있을까요??

혹은 Custom view를 띄울수 있을까요?
아 정말감사합니다 꼭필요한 글이였어요!!!