컴퓨터과학/Android 2014.11.22 23:08
 안드로이드에서 뿐만 아니라 일반적으로 프로그램을 작성할 때에 이벤트를 이용하여 처리해야 하는 부분이 있습니다.
예를 들면 버튼에 더블클릭을 구현해야 한다던지, 프로그래스바에서 진행상태를 화면에 표시하거나 하는 것들이 될 수 있습니다.

일반적으로 이벤트는  callback이라고도 하는데 어떠한 동작에 대한 피드백이라고도 할 수 있습니다. 

이벤트를 이용하여 프로그램을 작성하면 자연스럽게 뷰와 비즈니스로직이 분리되어 관리하기 쉬운 코드가 작성됩니다. 
물론 가독성도 나아지며, 코드도 훨씬 간결해집니다.

그러한 이벤트는 크게 세가지 정도로 구현 할 수 있습니다.
  • 안드로이드 이벤트 처리 방법
    1. 핸들러
    2. 옵저버
    3. 인터페이스
    4. 브로드캐스팅

이벤트 처리의 기본 구조는 메시지를 발생시키는 쪽과 구독하는 쪽이 있으며, 구독하는 쪽에서 구독 등록을 하면 발생시키는 쪽에서 이벤트 발생시 통지를 해줍니다.
코드만 다를 뿐 개념은 동일하다고 생각하면 됩니다.

 용도에 따라서 각각의 방법을 이용하면 되는데 (저는 핸들러 : 네트워크 처리, 옵저버 : 상태 관리, 브로드캐스팅 : 전역 통지가 필요한 경우에 주로 사용하고 있습니다.)

 이 포스트에서는 핸들러, 옵저버, 브로드캐스팅은 다루지 않고, 인터페이스를 이용하는 방법을 중심으로 다루도록 하겠습니다.
다른 방법은 다른 포스팅을 참조하여 주시면 감사하겠습니다.

 인터페이스를 이용하는 방법은 몇 가지 특징이 있습니다. 인터페이스 자체의 특징이라고도 할 수 있습니다. 

  • 명확함
     인터페이스를 이용하여 이벤트를 처리하는 방법은 다른 이벤트 처리 방법에 비하여 굉장히 큰 장점이 있습니다. 바로 명확하다는 것입니다.
    이름을 개발자가 원하는 대로 작성할 수 있어, (너무 제멋대로만 아니라면) 다른 개발자가 참조할 때에 해당 이벤트의 의도를 알 수 있습니다.
    버튼의 onClick() 이벤트는 누가 보아도 버튼을 클릭할 때 발생하는 이벤트라는 것을 알 수 있듯이, 다른 이벤트 처리 방법에 비해 굉장히 명확하다는 장점을 가지고 있습니다.

  • 이벤트 상속
     부모의 이벤트를 상속받아 처리 할 수 있는 장점이 있습니다. 물론 처리하지 않을 수도 있습니다. 상속관계를 통해 이벤트의 전달을 제한하거나 코드를 분리하여 작성 할 수 있습니다.

  • 추상화
     인터페이스는 추상화를 통해 실제 발생하는 이벤트를 런타임에 결정할 수 있습니다.


특징은 아무리 말해봐야 직접 느끼지 않으면 모르니 바로 코드를 뿌리겠습니다.

public class CustomCamera extends Camera {

   @Override
   protected void onPictureTaken(byte[] data, Camera camera) {
           // perform some events.
   }

     private ArrayList<ITakePictureEvent> mTakePictureEvent = new ArrayList<CustomCameraEventManager.ITakePictureEvent>(1);

     private void performPictureTakenEvent(Bitmap bitmap, Bitmap thumbnail){
          for(ITakePictureEvent event : mTakePictureEvent){
               event.onPictureTaken(bitmap, thumbnail);
          }    
     }
    
     private void performTakePictureErrorEvent(int error){
          for(ITakePictureEvent event : mTakePictureEvent){
               event.onError(error);
          }    
     }
    
     public void setOnTakePictureListener(ITakePictureEvent event){
          if(!mTakePictureEvent.contains(event)) {
               mTakePictureEvent.add(event);
          }
     }

     public void removeOnTakePictureListener(ITakePictureEvent event){
          mTakePictureEvent.remove(event);
     }

     public void removeAllOnTakePictureListener(){
          mTakePictureEvent.clear();
     }

     public interface ITakePictureEvent{
          public void onPictureTaken(Bitmap result, Bitmap thumbnail);
          public void onError(int error);
     }

}

 위 코드는 카메라에서 사진이 찍혔을 때에 그 결과를 반환하는 코드입니다. (전체 코드가 아닌 일부라 저 코드만으로는 동작하지 않습니다.)
그리고 편의를 위해 코드를 하나의 클래스에 넣었습니다.

우리는 byte[]로 나오는 결과를 적절하게 비트맵으로 변환하여 전달하는 이벤트를 만들어 볼 것입니다.
물론 그냥 카메라 클래스에서 제공하는 결과를 비트맵으로 변환해주는 메서드를 만들어도 되겠지만, 학습을 목표로 하는 것이니만큼 그냥 진행하도록 하겠습니다.

우선 코드를 하나씩 잘라서 설명하도록 하겠습니다.



  • 인터페이스(이벤트 리스너)
public interface ITakePictureEvent{
          public void onPictureTaken(Bitmap result, Bitmap thumbnail);
          public void onError(int error);
}

사진 찍을 때 사용하는 이벤트라는 느낌이 드는 인터페이스입니다.
onPictureTaken() 메서드는 사진을 찍은 결과 비트맵과 썸네일 비트맵을 이벤트 구독하는 쪽에 넘겨 줄 것입니다.
onError() 메서드는 에러 발생시 에러 아이디를 넘결 줄 것처럼 생겼군요.


  • 이벤트 배열
private ArrayList<ITakePictureEvent> mTakePictureEvent = new ArrayList<CustomCameraEventManager.ITakePictureEvent>(1);

구독 신청한 이벤트를 관리하기 위해 사용하는 배열입니다. 저는 ArrayList를 사용하였으나, 편의를 위해 한 것일 뿐 별다른 의도는 없습니다.
하나 이상의 이벤트를 동시에 구독할 수 있게 하기 위해 배열을 이용한 것이기 때문에 구독자가 반드시 하나라는 보장이 있다면 배열을 사용하지 않아도 됩니다.
외부에서 이벤트 구독자를 핸들링 할 수 없도록  접근 지정자를 private으로 지정하였습니다.


  • 이벤트의 구독 신청
public void setOnTakePictureListener(ITakePictureEvent event){
          if(!mTakePictureEvent.contains(event)) {
               mTakePictureEvent.add(event);
          }
}

외부에서 이벤트 구독 신청을 할 수 있도록 노출하는 메서드입니다. 중복된 이벤트는 추가하지 않는 구조입니다.


  • 이벤트 구독 취소
public void removeOnTakePictureListener(ITakePictureEvent event){
          mTakePictureEvent.remove(event);
}

public void removeAllOnTakePictureListener(){
          mTakePictureEvent.clear();
}

이벤트의 구독을 취소할 수 있도록 외부에 노출하는 메서드입니다. 해당 이벤트만 구독을 취소하거나 모든 이벤트를 취소할 수 있습니다.


  • 이벤트의 통지
private void performPictureTakenEvent(Bitmap bitmap, Bitmap thumbnail){
          for(ITakePictureEvent event : mTakePictureEvent){
               event.onPictureTaken(bitmap, thumbnail);
          }    
}
    
private void performTakePictureErrorEvent(int error){
          for(ITakePictureEvent event : mTakePictureEvent){
               event.onError(error);
          }    
}

이벤트를 통지하여 구독자에게 전달하는 메서드입니다. 이 메서드는 클래스의 내부에서만 호출하기 위해 접근지정자를 private으로 설정하였습니다.

이제 카메라 어디에선가 저 메서드를 호출하기만 하면 이벤트가 발생됩니다.

음...그런데 최초 이벤트는 어디서 발생시켜야 할까요?

안드로이드에서 제공하는 카메라의 경우는 onPictureTaken(byte[] data, Camera camea)의 형태를 가진 이벤트를 제공하고 있습니다.
카메라가 사진을 찍으면 그 결과를 반환하는 이벤트죠.
우리는 그 이벤트 안에서 우리가 만든 이벤트를 발생시키도록 하겠습니다.


  • 이벤트의 발생
@Override
protected void onPictureTaken(byte[] data, Camera camera) {
     Bitmap bitmap = getBitmapFromByteArray(data);
     Bitmap thumbnail = getBitmapThumbnail(bitmap);

     performPictureTakenEvent(bitmap, thumbnail);
}

​이 부분이 이벤트의 최초 시작점입니다.
getBitmapFromByteArray(), getBitmapThumbnail() 메서드는 그냥 가상으로 만든 메서드입니다. 의미상으로 이해하면 될 것 같습니다.


  • 이벤트의 구독과 처리
public class CameraActivity extends Activity implements ITakePictureEvent{

       CustomCamera mCamera = new CustomCamera();
       ImageView mImageView;
       ImageView mThumbImageView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_camera_main);
        mImageView = (ImageView) findViewById(R.id.image_view);
        mThumbImageView = (ImageView) findViewById(R.id.image_thumb_view);

        // 이벤트 구독 신청
        mCamera.setOnTakePictureListener(this);  
    }

       // 이벤트 발생시 이곳으로 통지가 옵니다.
      @Override
      public void onPictureTaken(Bitmap bitmap, Bitmap thumbnail) {
              // 이벤트에서 넘어온 부분을 처리
              mImageView.setImageBitmap(bitmap);
              mThumbImageView.setImageBitmap(thumbnail);
      }

      @Override
      public void onError(int error) {
               // 에러 처리
      }
}

이벤트를 구독 신청하고 통지된 이벤트를 처리하는 코드입니다. 
액티비티에서 이벤트를 구독신청하고, 이벤트 인터페이스를 구현하여 이벤트 발생시 통지를 통해 원하는 처리를 할 수 있습니다.

이러한 이벤트 처리를 통해 결과적으로 우리는 뷰(액티비티)와 비즈니스로직(byte[] to Bitmap)의 코드를 분리하였습니다.

다음 포스트에서는 커스텀 컨트롤을 작성하면서 커스텀 컨트롤에 대한 이해와 이벤트의 처리를 응용하는 방법을 소개하겠습니다.

감사합니다.



posted by 연식킴
컴퓨터과학/Android 2014.04.24 23:06

안드로이드 sdk에서 제공하는 나인패치툴(draw9patch)를 실행해 보면 상단에 show / hide bad patch 라는 버튼이 있다.

그 버튼을 누르면 화면에 빨간색 네모 박스가 보였다 / 사라졌다 하는데...

(bad patch가 없는 경우는 물론 안 보인다.)

과연 bad patch란 무엇일까?



간단히 설명하면 9패치 이미지가 늘어날 때에 그려지는 가변 구간이다.
bad patch가 존재하면 이미지가 늘어날 때 이미지의 일관성(Visual coherence)을 유지할 수 없다고 매뉴얼에 써있다.ㅋ


음... 여튼 왼쪽 부분의 1픽셀만 선택하면 bad patch가 사라지더라.


posted by 연식킴
컴퓨터과학/Android 2014.02.13 19:20

간혹 ImageView가 정사각형으로 나왔으면 하는 경우가 있다.

단순히 정사각형이라면 layout_width와 layout_height를 같은 수치로 넣어주면 되지만...

그런 경우 동적으로 사이즈가 늘어나진 않아 불편하다.

아래 사이트에 답이 있다.


http://android-layouts.com/category/tags/square


위 내용을 참고하여 간단히 정리하면 ImageView를 상속받는 클래스를 생성 후 onMeasure() 메서드를 오버라이딩하고 width와 height를 동일하게 맞춰주면 된다. (가로 세로 둘 중에 짧은 길이로 통일)


protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

 
	    //Get canvas width and height
	    int w = MeasureSpec.getSize(widthMeasureSpec);
	    int h = MeasureSpec.getSize(heightMeasureSpec);
 
	    w = Math.min(w, h);
	    h = w;
 
	    setMeasuredDimension(w, h);
	}


이 방법은 ImageView 뿐만 아니라 대부분의 컨트롤에 적용 가능하다.


<com.lovejb.example.Views.SquareImageView android:layout_width="match_parent" android:layout_height="match_parent"

 />


xml layout에서는 위와 같은 식으로 사용하면 된다. 

posted by 연식킴
컴퓨터과학/Android 2014.02.04 20:52

안드로이드에서 SQLiteDatabase 객체의 insert()메서드는 long형 값을 반환한다.

그 메서드 주석에 보면 해당 값을 row ID라고 표현하는데, 실제로 어떤 값인지 자세히 설명되어 있지 않다.


해당 값은 실제 SQLite에서 정의하는 ROWID와 동일한데 간단히 말하면 INTEGER형으로 선언된 PRIMARY KEY는 내부적으로 ROWID를 앨리어스 하게 된다. 


즉, SQLite에서 데이터 INSERT시 INTEGER PRIMARY KEY AUTOINCREMENT로 선언된 필드가 있다면 자동으로 증가된 ROWID값이 반환된다.


자세한 내용은 아래 링크를 참조하시라.

http://www.sqlite.org/autoinc.html


posted by 연식킴
컴퓨터과학/Android 2013.05.24 13:19

ListView와 데이터를 연결시 사용자정의 ArrayAdapter를 사용하는 경우 

아이템 삭제나 추가시 Adapter에 연결된 외부 배열 갱신 후

instanceOfAdapter.notifyDataSetChanged() 를 호출하여도 갱신이 안되는 경우가 있다.

그런 경우 Adapter 안에서 별도로 관리하는 배열과의 참조 여부를 확인하고 해당 배열까지 갱신하여야 notifyDataSetChanged() 호출시 정상적으로 갱신이 된다.


posted by 연식킴