Dev./Android

like miller 2011. 11. 17. 11:46

안드로이드 앱을 이것 저것 써보다 보면 어느 화면에서든지 항상 위에 떠있는 뷰를 사용하는 앱을 발견하게 될 것이다. 그 예로 battery info always라는 앱을 꼽을 수 있다.

*battery info always 설명 : http://blog.naver.com/gin073k?Redirect=Log&logNo=90127230480


그렇다면 저 앱은 어떻게 자신의 앱도 아닌데 바탕화면이던, 게임앱이던 항상 자기 뷰를 표시할 수 있을까?

그것은 안드로이드 최상위 window에 뷰를 넣었기 때문이다. 이 예제는 그 방법에 대한 예제이다.


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 tv;             //항상 보이게 할 뷰. 멤버필드로 선언

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

        tv = new TextView(this);        //뷰 생성
        tv.setText("이 뷰는 항상 위에 있다.");
        tv.setTextSize(TypedValue.COMPLEX_UNIT_SP, 18);
        tv.setTextColor(Color.BLUE);
       
        //최상위 윈도우에 넣기 위한 설정
        WindowManager.LayoutParams params = new WindowManager.LayoutParams(
        WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY,   //항상 최 상위에 있게
        WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH,       //터치 인식
        PixelFormat.TRANSLUCENT);                                                          //투명
       
        WindowManager wm = (WindowManager) getSystemService(WINDOW_SERVICE); //윈도 매니저
        wm.addView(tv, params);  //최상위 윈도우에 뷰 넣기. permission필요.
    }


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
            ................
            android:permission="android.permission.SYSTEM_ALERT_WINDOW" >
        </service>


4. 뷰 제거

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

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


5. 서비스 실행/중지 할 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));    //서비스 종료
    }
}


실행 결과


 앱 시작
뷰 추가
바탕화면


 
유투브 동영상


다음 지도


Plants VS Zombies 게임
 

 

 



*터치 이벤트는 '항상 최상위에 나오는 뷰 만들기2'를 참고하세요.



AlwaysOnTop.zip


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

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



이전 댓글 더보기
흠 질문 하나더 해볼게요 ^^;
지금 멀티터치& 무브이벤트를 최상위 윈도우에서 잡아보려구하는데 힘드네요; 원터치만되고 무브가안돼네요 -ㅅ-;;
저도 무브 이벤트를 받아보려고 시도하고 있습니다. 성공하면 소스와 함께 글을 올리겠습니다. 하지만 쉽지 않네요.
그르게욤 ㅠㅠ저도 3일째 시도중이에요
참고할 만한 소스를 찾긴찾았는데 swipepad 어플은 이걸 되게만든것 같더라구요
apk디컴파일해서 보고있습니다. 근데 이것도 만만친 않네요
텍스트뷰말고 이미지화면으로 나오게 할 수도있을까요?
텍스트 뷰 말고 이미지뷰도 가능 합니다. 또한 Surface뷰를 올려서 동영상을 재생시키면 갤럭시 S3에 있는 팝업플레이 기능이 가능합니다.
말씀하신 surfaceView를 올려봤는데 투명속성이 먹히지가 않네요 이유가 뭘까요?
SurfaceView는 원래 투명이 되지 않습니다. surfaceview의 크기를 조절하시면 그 외의 부분은 투명하게 처리 될 것입니다.
GLSurfaceView를 사용하여 해결하였습니다. ^^
해당 뷰를 클릭하는 것 말고 강제로 원하는 좌표에 터치 이벤트를 발생시켜 activity를 상속받은 클래스의 버튼을 클릭하고 싶은데 불가능한가요? 밤새 시도해 봤는데 잘 안되네요 ^^ 서로 다른 엑티비티라서 그런 것 같은데 될 것 같으면서 안되네요 ㅠ
음.. 정확히 무엇을 원하시는지는 잘 모르겠는데 원하는 좌표에 터치 이벤트 발생시킬 수 있습니다. 그리고 그런 이벤트를 다른 액티비티나 뷰에 전달 할 수 있습니다.
예를 들어 본문에서 3번째 이미지인 바탕화면 이미지에서 "이 뷰는 항상 위에 있다"를 클릭하면 인터넷 아이콘 위에 터치 이벤트를 발생시켜서 인터넷 앱을 실행시키고 싶다는 말입니다. 제가 해보니까 터치 이벤트가 발생은 하는데 실제로 터치한 효과는 나타나지 않아서 한 질문입니다.
아~ 그렇다면 서비스로 터치 이벤트를 받아서 다른 액티비티(다른 앱의 액티비티)에 터치 이벤트를 전달하여 다른 앱의 버튼이나 뷰에 이벤트를 전달 하고 싶다는 것이군요.
이부분은 상당히 힘들 것 같네요. 만약 그 다른 앱과 서로 사전에 입을 맞추어 연동하는 부분을 만들면 모를까 그렇지 않으면 힘들 것 같네요.
혹시 액티비티 전체를 항상 탚에 올려 놓을 수는 없나요? 뷰 하나는 보여 주신 것 처럼 되는데 액티비티 레이아웃에 있는 모든 뷰를 항상 탑에 있게 하는 방법이 있나요?
액티비티를 항상 위에 올릴 순 없구요 레이아웃은 가능합니다.
레이아웃도 뷰이기 때문에 레이아웃을 뷰 대신에 넣으시면 됩니다
이런거 찾고있었는데.. 좋은 정보 감사합니다!
안녕하세요?
궁금한게 있어서 여쭈어 봅니다.
저도 최상의 버튼을 올리고 버튼에 터치 이벤트가 들어오는 것 까지 했는데요
터치가 겹치는 부분까지 먹혀버리니깐 제가 할려고 하는게 막혀 버렸네요.
혹시 최상의 버튼에만 이벤트가 들어 왔을 때 최상의 버튼만 이벤트 먹게 끔 안되는 건가요? 겹치는 부분까지 이벤트가 먹으니 이걸 막는 방법은 없을까요?
아 그리고 아센 이후 부터는 터치 리스너가 안먹히나요? 진저에서는 터치 이벤트가 들어오는데 아센 부터는 터치 로그가 안찍히네요..터치가 안먹히나 봐요?
최상위 뷰에 터치 이벤트 관련해서는 조만간 다른 글을 작성할 것입니다.
해당 글에서 위 글의 문제와 님께서 문의하신 문제를 해결하고 모든 터치 이벤트를 받는 글이 될 것입니다.
힌트라도 조금 주시면 안될까요? 하다가 안되니 너무 답답해서 그렇네요.ㅠ
현재 이 글에서 제시한 방향과 비슷하지만 약간의 옵션들이 다릅니다. 그렇다고 제가 아직 완성하지 못했기에 뭐라고 드릴 말씁이 없습니다. 괜히 힌트라고 알려드렸는데 그게 아니면 괜히 삽질하시는 것이 되기 때문입니다. 죄송합니다. 현재 최상위 뷰에서 모든 터치 이벤트를 인식하게 되었지만 다른 문제가 발생하여 그 문제만 해결되면 바로 올리겠습니다.
아 그러시군요.ㅠ 해결이 빨리 되었습좋겟네요~ 좋은 정보 감사합니다~^^
AsyncTask로 AlertDialog 를 어느 엑티비티에서건 엑티비티간 전환이 일어나도
그 위에 무조건 띄우고 싶은데요
서버에서 받아오는 값이 오래 걸릴경우 인트로가 길어지는 단점을 보완하고자
그렇게 하려 합니다. 이래저래 방법을 찾다가 위의 블로그 내용을 보게 되었는데요
제가 하려는 거에 접목 할수 있을까요?
접목을 한다면 어떻게 해야 할까요?
그런 경우라면 굳이 이러한 방법을 쓰지 않아도 될 것 같네요. static으로 현재 보여지고 있는 액티비티를 가지고 있으면 될 것 같네요. 그리고 AsyncTask 의 onPostExecute() 메소드에서는 static 변수로 현재 보여지고 있는 액티비티 객체를 통해서 Dialog를 출력해 주면 될 것 같네요.
아센이후에는 진짜로 터치가 먹히지 않는건가요 ?
아샌이후에도 됩니다.
아센이후에 터치이벤트를 받을수있는것 확인 했구요.. 근데.. 터치를 잡아버리고 놔줘질 안네요.. 다른 것을 실행 시킬수없네요. 그러면 무슨 소용이죠?
징징이네 징징이

알려줘도 답이 없다
제가 애타게 찾던 소스입니다 ㅠㅠ
제 블로그에 살짝쿵 담아갑니다
감사합니다.
큰 도움이 되었습니다!
감사합니다!!
혹 text 나 버튼 및 이미지를 가장위인 시간표시, 베터리표시하는 부분으로 올리는 방법은 없나요..
mParams.gravity = Gravity.LEFT | Gravity.TOP;//왼쪽 상단에 위치하게 함.
결론은 여기에서 좀더 위로 올리고 싶습니다.
WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN 을 추가해주면 상태바 위에까지 표시가 됩니다.
제가 지금 액티비티랑 서비스를 따로 패키지 생성하여서 aidl을 통해 값을 전달하고 그 값에 따라 화면 뷰를 띄어주는 작업을 하고있는데요. 블로그에 올려주신대로 서비스쪽에 WindowManager를 넣으니까 생성된 액티비티가 없어서 그런지 계속 오류가 발생하더라구요..어떻게 해결할 방법은 없을까요??
에러 로그가 뭔가요?
정말 찾던 자료입니다!감사합니다
자유롭게 옴직이는 레이아웃은 어떤걸까요??
항상 최상위에 나오는 뷰 만들기2 를 참고하시면 됩니다.
http://blog.daum.net/mailss/35
안녕하세요 제가 현제 onTuoch로 뷰 플리퍼를 구현했는데 구현 한 페이지 에 버튼이 여러개 있는 activity가 오면 버튼에 onClick이 먼저 반응해서 페이지가 좌우로 이동이 잘 되 지않습니다 onTouch를 먼저 먹게 하는 방법이 없을까요 현제 작성자 님의 코드를 활용하면 구현 할 수 있나요 ㅠㅠ?
좋은일 하십니다. 소중한 자료 감사히 받겠습니다. 저도 기회가 되면 좋은일 하겠습니다.^^*
어제 댓글 남겼던 사람입니다. TYPE_PHONE에서는 터치좌표가 받아와지지만 액티비티를 벗어나면 0을 출력하고...
위 방식에서 터치이벤트를 달았을 때는 터치가 안받아와지는... 찾아보았더니

Starting from Android 4.x, Android team Android team fixed a potential security problem by adding a new function adjustWindowParamsLw() in which it will add FLAG_NOT_FOCUSABLE, FLAG_NOT_TOUCHABLE and remove FLAG_WATCH_OUTSIDE_TOUCH flags for TYPE_SYSTEM_OVERLAY window.
That's TYPE_SYSTEM_OVERLAY window won't receive any touch event on ICS platform.

라는군요... 결국엔 TYPE_PHONE을 써야하는데 그것 역시 안되니... 이젠 방법이 없는거 같은데... 앱스토어에 올라와있는 히로매크로 같은건 대체 어떻게 구현을 한것인지... 미치겠네요 ㅠ
좋은글 감사합니다