TouchEvent 좌표 오류: ViewTreeObserver.OnGlobalLayoutListener

 

 

TouchEvent로 해당 좌표에 ImageView가 나타나게 하고 싶었는데, 엉뚱한 곳에 나타남😂 (애매한 오른쪽 아래?)

 

TouchEvent에서 받아온 좌표값은 Screen 전체에서의 좌표 값이고,

그려지는 부분에선 Layout을 기준으로 하기 때문에 Actiobar나 화면 상태표시줄 등등에 의해 오차가 생긴 거였다!!

A view tree observer is used to register listeners that can be notified of global changes in the view tree. Such global events include, but are not limited to, layout of the whole tree, beginning of the drawing pass, touch mode change.... A ViewTreeObserver should never be instantiated by applications as it is provided by the views hierarchy. Refer to View.getViewTreeObserver() for more information.

공식문서: https://developer.android.com/reference/android/view/ViewTreeObserver.html

 

대충 ViewTreeObserver를 이용하여 리스너를 등록하고 리스너를 통해 뷰의 크기나 위치를 가져오는 시점으로 사용하면 값을 가져올 수 있다는 뜻👌

처음에 Activity에서 구현했었는데, Fragment로 바꾸면서 TouchEvent가 잘못 찍히는 현상이 사라졌으나,

나중에 기억하기 위해서 남깁니다..ㅎㅎ(주임님이 도와주셨는데 넘... 헤맸던 부분이라 꼭 기억하고 싶었습니댱...)

 

그래서 코드상 fragment의 구조로 되어있는데 onCreateView 👉 onCreate에서 확인할 수 있습니다!

( 주석처리 풀어서 급하게 작성하느라 수정은 못했습니다^^;; 불친절한 블로거ㅎㅎㅠㅠㅠㅠ)

 

x좌표와 y좌표를 저장할 변수 선언.
    // Point
    private int mLayoutMainX = -1;
    private int mLayoutMainY = -1;

 

onCreate :: getViewTreeObserver().addOnGlobalLayoutListener
   @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        mView = inflater.inflate(R.layout.fragment_screentouch, container, false);

        mLayoutMain = mView.findViewById(R.id.layout_main);
        ...
        mLayoutMain.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {
                Log.d(TAG, "onGlobalLayout! (layout_main)");

                mLayoutMain.getViewTreeObserver().removeOnGlobalLayoutListener(this);
                Log.i(TAG, "layout_main w: " + mLayoutMain.getWidth() + ", h: " + mLayoutMain.getHeight());

                int[] location = new int[2];
                mLayoutMain.getLocationOnScreen(location);
                Log.i(TAG, "layout_main location: " + Arrays.toString(location));
                mLayoutMainX = location[0];
                mLayoutMainY = location[1];
            }
        });
        return  mView;
    }
 

mLayoutMain(최상위 layout)에 getViewTreeObserver를 이용하여 리스너를 등록하고

onGlobalLayout을 override (new 연산자 입력하면 자동으로 생성됨!)

  • removeOnGlobalLayoutListener(this);  
    • 리스너가 중첩되는 것을 막기 위해 반드시 해주어야한다고 함!!
  • mLayoutMain.getLocationOnScreen(location);
    •  touchEvent의 좌표값 받아올 수 있음.

 

 

추가로 사용 예시.. 같은 건데.. 그냥 참고만 해주세요😅

isTouchInside(View view, int x, int y) :: view 안을 터치하면 result를 반환하는 method
  private boolean isTouchInside(@NonNull View view, int x, int y) {
        int[] location = new int[2];
        view.getLocationOnScreen(location);
        int realRight = location[0] + view.getWidth();
        int realBottom = location[1] + view.getHeight();

        Log.i(TAG, String.format("성공범위 (x: %d ~ %d, y: %d ~ %d", view.getLeft(), realRight, view.getTop(), realBottom));
        Log.d(TAG, String.format(" touched x: %d, y: %d", x, y));

        boolean result = (x >= view.getLeft() && x <= realRight && y >= view.getTop() && y <= realBottom);

        return result;
    }
 

Circle(ImageView)의 위치를 이용하여 Width와 Height를 계산하여 touchEvent가 발생할 때마다 호출하게 함.

Boolean 값을 return하고 조건문을 활용하여, 기능 추가함.

 

setCirclePoint(int x, int y) :: view 밖을 터치했을 때 실행되는 method

view 매개변수가 주어졌을땐 getLocationOnScreen 메소드를 사용하여 스크린 상의 위치를 얻을 수 있었으나,

매개변수가 주어지지 않을 때는 View.getViewTreeObserver에 아래와 같이 Listener 등록하여 사용.

 private void setCirclePoint(int x, int y){
        int realX = x -mIvCircle_point.getWidth()/2;
        int realY = y - mIvCircle_point.getHeight()/2;

        mIvCircle_point.setX(realX);
        mIvCircle_point.setY(realY);
        setValueAnimation(mIvCircle_point, Color.RED);
        Log.i(TAG, "touch outside");

        mIvCircle_point.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {
                View view = mLayoutMain.getChildAt(0);
                view.getViewTreeObserver().removeOnGlobalLayoutListener(this);

                int[] location = new int[2];
                view.getLocationOnScreen(location);
                setValueAnimation(mIvCircle_point, Color.RED);
            }
        });
    }
 

 


++ 추가) 현재 화면(Display)의 크기를 알고 싶을때

        WindowManager windowManager = (WindowManager) getActivity().getSystemService(Context.WINDOW_SERVICE);
        Display display = windowManager.getDefaultDisplay();
        final Point realSize = new Point();
        display.getRealSize(realSize);
        Log.d(TAG, "real Display Size: " + realSize.toString()); // realSize = 디스플레이 크기
 

WindowManager 사용하여 가져올 수 있음. (실제로 사용하지는 않았지만 이상하게 찍히는 원인을 알 수 있어서 추가)

 

첫 게시글이라 조금 불친절해요... 양해부탁드립니다 ㅎㅎ

 

♡ 감사합니다!! 뿅!!

 

 

 

+ Recent posts