본문 바로가기

구글과인터넷/안드로이드

안드로이드 키입력,터치입력 관련 (방향키입력, 위치이동키 등등)

제가 다른 클래스에서 액티비티에 있는 onKeyDown() 이벤트함수를 호출했습니다.

KeyEvent e = new KeyEvent(KeyEvent.ACTION_DOWN,KeyEvent.KEYCODE_DPAD_DOWN);
mFaceDetect.onKeyDown(KeyEvent.KEYCODE_DPAD_DOWN,e);

이와 같은 방법으로 호출을 해서 keyDown()가 호출되긴 하는데요...
코드를 보시면 알겠지만 방향키 호출입니다.

특별히 다른 일 없이 그대로 방향키가 호출되면 됩니다...

public boolean onKeyDown(int keyCode, KeyEvent event) {
  if(KeyEvent.ACTION_DOWN == event.getAction()  && keyCode == KeyEvent.KEYCODE_DPAD_DOWN){
   super.onKeyDown(keyCode, event); 
  }
  return super.onKeyDown(keyCode, event);
 }
그래서 기본 함수 형태로 했는데 아래방향 키가 안눌러지네요..
분명 keyDown()에 ACTION_DOWN 도 호출되고KEYCODE_DPAD_DOWN 도 호출 되었거든요
근데 왜 아래방향으로 키가 눌려지는 현상이 안일어날까요?
   super.onKeyDown(keyCode, event); 
이 함수 쓰는 것이 맞나요?
아님 제가 틀린건가요?

아래방향키가 KEYCODE_DPAD_DOWN 맞나요?
실제 아래방향키를 눌러보니까 로그가 안뜨네요? 아래방향키 keyCode가 어떻게 되나요?

 
댓글
2010.10.06 10:30:01
경기도공대생

public boolean onKeyDown(int keyCode, KeyEvent event) {
  // TODO Auto-generated method stub

  if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT)
   //  
else if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) 
   //  
else if (keyCode == KeyEvent.KEYCODE_DPAD_UP)
  //
  else if (keyCode == KeyEvent.KEYCODE_DPAD_DOWN) 
  //
  return super.onKeyDown(keyCode, event);
 }

이렇게해서 사용중입니다. 참고하세요 4방향 버튼 다 잘눌려요


//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

출처: http://www.androidpub.com/index.php?mid=android_dev_qna&page=1532&document_srl=359861

에뮬레이터에 있는 방향키 왼쪽 오른쪽 위아래 이것을 이용해서 이미지 방향을 바꿔야하는데요

방향키를 이용하려면 어떻게 해야되나요??

 
댓글
2010.05.18 18:51:20
And
Activity에서 onKeyDown 함수를 override 하시구요.

Keycode 가  정확히 기억은 안나는데 keycode에 DPAD를 찾아보셔요



////////////////////////////////////////////////////////////////////////////////////////////

///////////////////////////////////////////////////////////////////////////////////////////


출처: http://www.androidside.com/bbs/board.php?bo_table=421&wr_id=34


이 시간에는 Touch Event와 Key Event 처리에 대해서 알아보도록 하겠습니다. 지난 강좌에 작성했던 내용에 추가합니다. 먼저 Touch Event부터 시작하겠습니다.
 
1. Touch Event 처리 

Touch Event는 사용자가 폰의 화면을 터치했을 때 발생하는 이벤트입니다. 에뮬레이터에서는 왼쪽 마우스로 AVD를 클릭하면 Touch Event가 발생합니다. 이벤트 핸들러를 자동으로 만들어 볼까요? 

이클립스나 MotoDev Studio의 편집창(Editor)에서 커서를 GameView class 안에 아무곳이나 위치시키고 마우스 오른쪽 버튼을 클릭합니다. 팝업 메뉴가 나타나면 [Source - Override/Implement Methods...]를 클릭합니다.
 






 

이어 나타나는 [Override/Implement Methods] 창에서 onTouchEvent(Motion Event를 지정합니다. 소스 코드 삽입 위치는 After 'mHandler'로 지정합니다.
 






 
우리가 원하는 것이 만들어 졌군요. 여러분이 만든 위치가 GameView의 끝을 벗어나 있는 경우에는 위로 옮겨주시기 바랍니다. 







 

Touch Event는 손가락으로 화면을 눌렀을 때(Down), 손가락을 뗐을 때(Up), 손가락을 누른채로 이동할 때(Move) 각각 이벤트가 발생하며, 이벤트가 발생한 View의 위치를 실수(float) 형태로 구해줍니다. 물론  AVD에서는 손가락 대신에 마우스를 사용합니다. 이 값을 캐릭터의 x, y 좌표로 설정해 주면 View를 터치해서 캐릭터의 위치를 옮길 수 있습니다. 프로그램을 다음과 같이 수정합니다. 





 

앞에서 언급 했듯이 TouchEvent는 DownUpMove가 동일한 이벤트 핸들러(Handler)를 사용하므로 먼저 현재 발생한 이벤트가 어느 것인지를 판단할 필요가 있습니다. 위의 코드에서 보듯 event.getAction() 함수가 현재 발생한 이벤트를 알려주므로 이것을 이용하여 발생한 이벤트를 판단합니다. 

이클립스나 MotoDev Study가 자동으로 만들어 주는 메소드의 맨 마지막 문장은
 
    return super.onTouchEvent(event); 

로 되어 있습니다. super는 자신(this)의 상위 class를 의미합니다. 위의 문장은 터치 입력을 super도 처리할 수 있도록 하는 기능인데, super가 이벤트를 가져가 버리면 우리의 메소드가 말을 잘 듣지 않는 경우가 생기게 됩니다. 그래서 터치를 super로 넘기지 않고 그냥 return true나 return false로 Touch 이벤트를 마무리 합니다. 

현재 우리의 프로그램에서는 return true나 return false가 똑같이 작동을 합니다. true와 false의 차이점은 강좌가 계속 진행되면서 게임에 메뉴를 만들어 넣을 때 그 차이를 알게 될 것이므로 우선은 그냥 넘어가기로 합니다. 위의 액션을  MotionEvent.ACTION_DOWNMotionEvent.ACTION_UPMotionEvent.ACTION_MOVE로 바꿔서 실행시켜 보면 각각 다른 결과가 나옵니다.

      ACTION_DOWN : 마우스를 누른 즉시 결과가 나타난다
      ACTION_UP      : 마우스 버튼에서 손을 뗐을 때 결과가 나타난다 
     ACTION_MOVE  : 버튼을 누르고 마우스를 움직이면(드래그) 이벤트가 계속 발생하므로 캐릭터가 마우스를 쫒아 다닌다
 
             ACTION_DOWN  




                                      

ACTION_UP                                  





      ACTION_MOVE           





       

Touch 이벤트에서 어떤 액션을 사용할 것이냐는 상황에 따라 다릅니다. 즉각적인 결과가 나타나야 하는 경우에는 당연히 ACTION_DOWN이겠지만 손가락(마우스)로 캐릭터의 위치를 이동해야 하는 경우라면 ACTION_MOVE를 사용하겠지요. 

2.  View를 드래그해서 캐릭터의 속도와 방향 바꾸기 

요즈음의 스마트폰은  손가락으로 위젯의 위치를 이동하는 기능이 있습니다. 위젯이 망가질세라 아주 조심스럽게 끌고가는 사람도 있고, 저러다 액정이 부서지지 않을까 싶을만큼 과감한 스피드로 위젯을 끌고다니는 사람이 있습니다. 

현재 캐릭터는 프로그램에 설정된 속도(dx=4, dy=6)로 이동하고 있습니다. 이번에 할 작업은 마우스로 화면을 드래그하면 캐릭터가 드래그한 방향과 속도로 이동하도록 프로그램을 변경해 보려고 합니다. 마우스를 위에서 아래로 드래그하면 캐릭터는 아래쪽으로 드래그한 속도로 계속 이동하는 것입니다. 물론 캐릭터가 화면을 빠져나가면 곤란하므로 벽과 충돌하는 것은 현행대로 진행합니다. 

이제 프로그램이 조금 복잡해 지나요? 우선 드래그한 방향과 속도를 어떻게 구해야 할 지 생각해 보도록 합니다. 아래 그림을 보시죠. (x1, y1)에서 (x2, y2)로 마우스를 드래그하면 마우스의 움직임은 수평으로 dx, 수직으로 dy만큼 이동한 거리가 됩니다. 





 

이 값을 캐릭터가 반복해서 이동할 거리로 설정해 주면 캐릭터는 계속해서 마우스가 움직였던 방향과 속도로 이동하게 될 것입니다. 단, 화면상의 실제 이동 거리인 dx와 dy값은 너무 클 것이므로 적절히 축소(1/10 정도)해서 사용합니다. Touch 이벤트를 다음과 같이 사용해야겠네요. 

    ACTION_DOWN : (x1, y1)의 좌표를 구한다 
    ACTION_UP  : (x2, y2)의 좌표를 구한 후 캐릭터의 속도를 설정한다. 
    dx = (x2 - x1) / 10; 
    dy = (y2 - y1) / 10; 

변수가 몇 개 더 필요하겠군요. 그리고 위의 식은 나눗셈을 사용하고 있으므로 변수를 모두 실수형(float)으로 바꾸는 것이 좋을 듯 합니다. 변수 선언 부분을 다음과 같이 변경하고, 새로운 변수를 추가합니다. 



 


프로그램을 손 본 김에 벽과 충돌하는 부분도 제대로 만들어 보도록 하죠. 



  
이제 Touch 이벤트 부분입니다. 






ACTION_DOWN에서 시작점을 저장한 후 ACTION_UP에서 끝점을 설정하고 점 사이의 거리를 계산하도록 되어 있습니다. 이제 프로그램을 실행 시킨 후 View를 드래그하면 그 방향과 속도로 캐릭터가 이동하는 것을 볼 수 있습니다.
 



 


3. LogCat 으로 프로그램 디버깅하기 

프로그램이 내 생각대로 잘 움직여 주면 좋은데 실제로는 그렇지 않죠? 물론 저도 마찬가지이구요. 문법적인 오류는 이클립스가 지적해 주지만 논리적인 오류는 찾아내기가 아주 어렵습니다. 현재 변수의 값은 어떻게 변하고 있는지를 프로그램 실행중에는 확인할 방법이 없으니까요. 

LogCat은 현재 실행중인 내 프로그램은 물론 안드로이드의 상태도 표시해 주는 아주 유용한 도구입니다. 일단 LogCat을 보이도록 해야겠죠? [Window - Show View - Other...]를 클릭합니다.
 



 

[Android] 폴더에 LogCat이 있습니다. 



 

LogCat을 설치하면 이클립스 왼쪽의  Pakage Explorer 자리에 위치하게 됩니다. LogCat은 가로폭이 넓은 것이 좋으므로 LogCat 창을 끌어다 이클립스 아래쪽으로 이동합니다. 이렇게 보이겠죠? 



 

LogCat은 현재 안드로이드에서 일어나는 각종 이벤트 뿐만 아니라 사용자가 입력한 내용도 모두 표시됩니다. 프로그램에서 LogCat에자료를 표시할 때에는 다음과 같은 형식을 사용합니다. 

         Log.[v/d/i/r/e]("제목", "출력할 값"); 

제목과 출력할 값 모두 문자열 형태로 지정합니다. LogCat에 대한 자세한 내용은 안드로이드 사이드의 강좌/학습 카테고리를 검색하시면 자세한 정보를 얻을 수 있을 것입니다. LogCat에서 'v' 옵션을 사용하면 프로그램이 완성되서 배포판을 만들 때 LogCat의 기능이 자동으로 정지되지만 다른 옵션은 배포판에도 포함되므로 가능하면 Log.v( )로 츨력하는 것이 좋습니다. 

우선 메시지 핸들러에서 (x, y) 값이 어떻게 변하고 있는지 확인해 보도록 할까요? 메지지 핸들러에 다음과 같은 1행을 추가합니다. 



 

이제 프로그램을 실행하고 LogCat에 출력되는 값을 확인해 봅니다. 



 

변수의 값은 제대로 출력되는 것 같은데 한글은 깨져서 출력되는군요. LogCat에 한글을 출력하는 방법은 안드로이드 사이드의 강좌/학습에도 설명되어 있으니까 각자 찾아보시기 바랍니다. 이제 AVD의 돌아가기나 홈 키를 눌러 프로그램을 끝냅니다. 

그런데 우린 프로그램을 끝냈다고 생각했는데도 LogCat을 보면 여전히 실행중인 상태로 나오고 있군요. 계속해서 x와 y의 값이 표시되고 있는 것이 보입니다. AVD의 설정(Settings) 버튼을 눌러서 어떤 상태인지 알아보도록 하겠습니다. 우리가 만든 프로그램이 여전히 백그라운드에서 실행중인 상태로 표시됩니다. 

        














 

불필요한 프로그램이 백그라운드에서 실행하고 있으면 CPU와 메모리를 차지하기 때문에 전체적인 성능이 떨어질 수 있습니다. 그래서 프로그램은 실행이 끝나면 메모리에서 완전히 제거되도록 해 줘야 합니다. 이제 키보드에서 키를 입력받아 프로그램을 종료시키는 방법에 대해 알아보기로 하겠습니다. 


4. 키보드 입력을 받아 프로그램 종료시키기 

먼저 키보드 처리를 위해 키보드 이벤트 핸들러를 만들어야 겠군요. 이것도 자동으로 만들도록 하겠습니다. 앞에서 Touch 이벤트 핸들러 만드는 방식으로 처리합니다. [Override/Implement Methods] 창이 나타나면 onKeyDown(int, KeyEvent)를 선택합니다. 소스 코드 삽입 위치는 After 'onTouchEvent'로 지정합니다.
 



 



 

새로운 이벤트 핸들러가 만들어졌군요.  



 

여기에서 keyCode를 읽어 프로그램을 종료하도록 하면 됩니다. PC에서는 프로그램을 종료시킬  때 [Ctrl+X],  [Alt+X]와 같은 키를 이용하는데 폰에는 그런 키가 없고, 더우기 스마트폰은 메뉴, 취소 등 기본적인 버튼만 있지 키 자체가 없므로 게임을 키보드로 제어하는 것은 조금 곤란합니다. 그래서 여기서는 사용자가 [↑] 키를 누르면 프로그램을 종료하도록 합니다. 

안드로이드에서 프로그램을 종료시키는 방법은 다음과 같은 3가지가 있습니다. 

      ① finish()                              현재 활성중인 Activity를 종료한다 
      ② finishActivity(<상태 코드>);    현재 활성중인 Activity를 종료하고 그 결과를 호출 Activity에 전달한다 
      ③ System.exit(<상태 코드>)      프로그램을 강제로 종료한다 

①과 ②는 걑은 기능을 하는 것으로, ②는 자신을 호출하는 상위 Activity에 결과를 전달할 때 사용하며 ③은 강제로 프로그램을 종료시키는 함수로 이것을 사용하면 모든 Activity가 종료됩니다. 

여기서 잠깐 Activity에 대해서 부연 설명을 하면, 프로그램이 여러 개의 창으로 구성되어 있다고 할 때 각각의 창을 Activity라고 생각하면 됩니다. 우리가 이 게임의 오프닝 화면을 별도로 만들면 그것도 하나의 Activity가 되겠죠? 우리는 현재 1개의 Activity만 사용하고 있으므로 ①, ②, ③이 같은 기능을 한다고 볼 수 있지만 여러 개의 Activity를 가진 프로그램에서 System.exit()를 사용하면 조금(사실은 아주 많이) 곤란합니다. 

스레드 Handler는 Activity의 종료와 상관없이 계속해서 진행하려는 성질이 있습니다. 그렇기 때문에 프로그램을 정상적으로 종료시키기 위해서는 먼저 스레드나 Handler 중지시킬 수 있는 방법을 미리 강구해 두어야 합니다. 대부분 별도의 변수를 마련해 두고 스레드 내부에서 반복할 것인지 중단할 것인지를 결정하도록 하는 방법을 사용합니다. 

Handler 부분을 다음과 같이 수정합니다. canRun 이라는 변수를 하나 도입해서 이 값을 참조해서 반복할 것인지를 판단하는 구조입니다. 

 





키 이벤트 처리 부분은 다음과 같이 간단히 작성하면 됩니다. 



 


물론 위와 같이 처리 하기 위해서는 변수 선언 영역에서 다음과 같이 변수를 선언해 두어야 할 것입니다. 

       boolean canRun = true; 

이제 프로그램을 실행한 후 [] 키를 누르면 프로그램이 끝나고 AVD의 메인창으로 넘어가는 것을 확인할 수 있을 것입니다. AVD의 키 반응성이 좋지 않으므로 실제로는 두어번 눌러줘야 프로그램이 끝나게 되는군요. 다음은 지금까지의 전체 소스입니다. 


package com.Game01;

import android.app.*;
import android.content.*;
import android.graphics.*;
import android.os.*;
import android.util.*;
import android.view.*;

public class Game01 extends Activity {
  
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // 전체 화면 사용하기
        requestWindowFeature(Window.FEATURE_NO_TITLE);   
        getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, 
                             WindowManager.LayoutParams.FLAG_FULLSCREEN);

        setContentView(new GameView(this));
    }   

    //-----------------------------------
    //       Game View
    //-----------------------------------
    class GameView extends View {
         int width, height;                                  // 화면의 폭과 높이
         float x, y;                                            // 캐릭터의 현재 좌표
         float dx, dy;                                        // 캐릭터가 이동할 방향과 거리
         int cw, ch;                                          // 캐릭터의 폭과 높이
         int counter;                                         // 루프 카운터            
         Bitmap character[] = new Bitmap[2];   // 캐릭터의 비트맵 이미지
         float x1, y1, x2, y2;                             // Down, Up
         boolean canRun = true;
     
        //-----------------------------------
        //   Constructor - 게임 초기화
        //-----------------------------------
        public GameView(Context context) {
               super(context);
   
               Display display = ((WindowManager) context.getSystemService(Context.WINDOW_SERVICE))
                                          .getDefaultDisplay();
               width = display.getWidth();              // 화면의 가로폭
               height = display.getHeight();            // 화면의 세로폭
               x = 100;                                         // 캐릭터의 현재 x위치
               y = 100;                                         // 캐릭터의 현재 y위치 
               dx = 4;                                          // 캐릭터가 x축으로 이동할 거리
               dy = 6;                                          // 캐릭터가 y축으로 이동할 거리
            
               // 캐릭터의 비트맵 읽기
               character[0] = BitmapFactory.decodeResource(getResources(), R.drawable.rabbit_1);
               character[1] = BitmapFactory.decodeResource(getResources(), R.drawable.rabbit_2);
               cw = character[0].getWidth() / 2;               // 캐릭터의 폭/2
               ch = character[0].getHeight() / 2;              // 캐릭터의 높이/2
            
               mHandler.sendEmptyMessageDelayed(0, 10);
        }
  
        //-----------------------------------
        //       실제 그림을 그리는 부분
        //-----------------------------------
        public void onDraw(Canvas canvas) {
               x += dx;                              // 가로 방향으로 이동
               y += dy;                              // 세로 방향으로 이동
               if (x < cw) {                        // 왼쪽 벽
                    x = cw;
                    dx = -dx;
               } else if (x > width - cw) {   // 오른쪽 벽
                    x = width - cw;
                    dx = -dx;
               } else if (y < ch) {               // 천정
                    y = ch;
                    dy = -dy;
               } else if (y > height - ch) {    // 바닥
                    y = height - ch;
                    dy = -dy;
               }
               counter++;
               int n = counter % 20 / 10;
               canvas.drawBitmap(character[n], x - cw, y - ch, null);
        } // onDraw 끝
  
        //------------------------------------
        //      Timer Handler
        //------------------------------------
        Handler mHandler = new Handler() {               // 타이머로 사용할 Handler
             public void handleMessage(Message msg) {
              if (canRun == true) {
                   invalidate();                                              // onDraw() 다시 실행
                   Log.v("변수 값", "x=" + x + "  y=" + y);
                   mHandler.sendEmptyMessageDelayed(0, 10); // 10/1000초마다 실행
              } else
                   finish();
              }
        }; // Handler

        //------------------------------------
        //      onTouchEvent
        //------------------------------------
        public boolean onTouchEvent(MotionEvent event) {
             if (event.getAction() == MotionEvent.ACTION_DOWN) {
                  x1 = event.getX();                   // 버튼을 누른 위치  
                  y1 = event.getY();
             } else if (event.getAction() == MotionEvent.ACTION_UP) {
                  x2 = event.getX();                   // 버튼을 이동한 후 손을 뗀 위치   
                  y2 = event.getY();
                  dx = (x2 - x1) / 10;                 // 버튼의 거리
                  dy = (y2 - y1) / 10;  
                  x = x1;                                   // 캐릭터의 현재 위치를 버튼을 누른 곳으로 설정
                  y = y1;
             }
             return true;
        } // onTouchEvent

        //------------------------------------
        //      onKeyDown
        //------------------------------------
        public boolean onKeyDown(int keyCode, KeyEvent event) {
            if (keyCode == KeyEvent.KEYCODE_DPAD_UP) {
                 canRun = false;
            }
           return true;
        } // onKeyDown

    } // GameView 끝
    
} // 프로그램 끝
  



지금까지 몇 회에 걸쳐 사용자 View를 만들고 Handler를 이용해서 반복처리하는 과정을 알아 보았습니다. 이 과정은 초심자들이 이해하기 쉽도록 프로그램을 간단히 만든 것인데 실제의 프로그램은 이와 같은 방식으로 작성하지는 않습니다. 

다음 강좌부터는 실제의 게임 프로그램에서 사용하는 SurfaceView와 Thread를 이용해서 고속 처리를 하는 과정에 대해 학습하게 됩니다. 물론 게임의 배경도 들어가고, 연재가 진행되면 배경화면을 스크롤하는 과정도 다루겠죠. 

SurfaceView와 Thread가 나타나면 프로그램이 상대적으로 길어지고 많이 복잡해집니다. 그렇지만 지금까지 학습했던 과정을 잘 이해하고, 변수 선언 부분과, 초기값을 할당하는 부분, 실제로 반복처리를 하는 부분 등을 잘 구분할 줄 안다면 다음 강좌도 잘 따라올 수 있을 것으로 생각합니다.   
    

/////////////////////////////////////////////////////////////////////////////////////

출처: http://zerosum30.egloos.com/1399280

UI환경에 따라  기본 입력키 만을 입력 받아 버튼이나 리스트를 이동해야 할 때가 있다. 이때 현재 위치를 보여주는 것이 포커스다. UI를 한 ACTIVITY 안에  설계하고 리소스 사용에 유의 한다면 안드로이드가 focusable 한 모든 곳을 포커스 한다. 기본방향키도 default로 아주 잘 설정 되어 있다.  하지만, 특정 레이아웃을 상속받아 클래스를 구성하고 이를 xml파일에서 패키지 명으로 불러서 사용하거나 내부적으로 service나 broadcast이 리소스를 배려하지 않고 마구 돌아가고 있다면, 포커스가 정상적으로 동작하지 않는다. 심지어 포커스가 되고 있는 와중에도 내부 프로세스가 돌아가면서 포커스가 없어지기도 한다. -_-;;
이렇게 touchEvent를 사용할 수도 마우스로 클릭할 수도 없는 상황에서 KetEvent를 받아 직접 처리할 수 있는 데 이 방법을 사용할 때 리스트의 갯수가 가변적이던가 다른 클래스로 이동해야할 때 포커스가 말을 드럽게 안듣는 경우가 있다.
이럴때 keyEvent를 2중구조로 만들어주면 아주 쉽게 클래스를 넘나들수 있고 리스트의 갯수와 상관없이 깔끔하게 포거스가 가능하다.  

일단 소스를 보자.

  private RalativeLayout b;
       testListener = new OnKeyListener() {
            public boolean onKey(View v, int keyCode, KeyEvent event) {
                switch (keyCode) {
                case KeyEvent.KEYCODE_DPAD_UP:
                case KeyEvent.KEYCODE_DPAD_DOWN:
                case KeyEvent.KEYCODE_DPAD_LEFT:
                case KeyEvent.KEYCODE_DPAD_RIGHT:
                             }         
                return b.dispatchKeyEvent(event);
            }

        };
키를 받아  구현하는 부분은 생략했다. 이 소스에서 중요한 부분은 마지막 리턴부분이다.
리턴에 넘겨주는 부분에서 b는 어떤 레이아웃이던 상관없이 이 레이아웃 안에 일어나는 모든 키 이벤트를 넘긴다는 것이다.
한번 누르면 다운 업 이렇게 2번 값이 넘어가게 되는 것이다. 즉 이동이 일어난다면 ture가 넘어가고 리스트가 끝나거나 더이상 포커스가 없는 방향으로 키가 들어오면 false 가 넘어가는 것이다.