항상 최상위에 나오는 뷰 만들기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 on_clickListener {
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        
        findViewById(R.id.start).seton_clickListener(this);        //시작버튼
        findViewById(R.id.end).seton_clickListener(this);            //중시버튼
    }
    
    @Override
    public void on_click(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

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

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

+ Recent posts