2012년 10월 15일 월요일

안드로이드 갱신 후 스크롤 제일 아래로

mScroll 은 스크롤뷰

Handler scrollHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            scrollDown ();
        }
    };
   
   
   
    private void scrollDown() {
        mScroll.post(new Runnable() {
            @Override
            public void run() {
                mScroll.fullScroll(ScrollView.FOCUS_DOWN);
            }
        });
    }

[Android] startActivityForResult(), onActivityResult() 사용하기


안드로이드 코딩을 할 때, 가장 기본적인 메소드 중 하나가 바로 startActivityForResult()이다.
하지만 웹 어디를 뒤져봐도 사용법을 쉽게 설명해 둔 페이지는 찾아보기가 힘들더라. (망할것)
나처럼 완전 삽질을 하는 사람이 생겨나는 것을 막고자 이렇게 포스팅을 해보려 한다.

안드로이드의 Activity들은 startActivityForResult()라는 메소드를 통해 sub activity를 만들고 Activity끼리 서로 데이터를 교환할 수 있다.
그 사이에는 'extra'라고 하는 통로가 존재하는데, 이 부분을 잘 알아야 한다.

Activity A가 Activity B를 서브 엑티비티로서 부르고 그로부터 결과값인 데이터를 전송받기 위해서는 어떻게 해야 할까?
먼저 삽화를 통해 기본 원리를 파악해보도록 하자.

다음과 같은 원리이다.
Activity A가 startActivityForResult를 통해서 Activity B를 호출하면,
B는 종료가 되면서 Result 값을 통해 Extra 꾸러미를 넘긴다.
그러면 Activity는 Extra 꾸러미 안에 있는 데이터들을 꺼내서 사용할 수 있는 것이다.

그렇다면 실제 구현 과정에서 어떤 작업들을 해주어야 하는지 살펴보자.

[Activity A 사이드]
Activity A에서 구현되어야 할 부분은,
첫째, int값의 requestCode 값을 설정해준다.
둘째, Intent를 만들어 Activity B를 실행시킨다.
셋째, onActivityResult()를 통해 각 requestCode값에 해당하는 결과값을 받아온다.

[Activity B 사이드]
Activity B에서 구현되어야 할 부분은,
첫째, Intent를 만들어 데이터 꾸러미를 Intent에 추가시킨다.
둘째, 결과값을 보내면서 Extra 꾸러미를 가지고 있는 Intent를 함께 넘겨준다.

간단하지 않은가? 그럼 코드에서는 이와같은 것들이 어떻게 구현이 될까?

[Activity A 사이드]
private static final int B_ACTIVITY = 0;

public void onCreate(){
...
Intent a_i = new Intent(this, B.class);
startActivityForResult(a_i, B_ACTIVITY);
...}

public void onActivityResult(int requestCode, int resultCode, Intent intent){
super.onActivityResult(requestCode, resultCode, intent);

switch(requestCode){
case B_ACTIVITY: // requestCode가 B_ACTIVITY인 케이스
if(resultCode == RESULT_OK){ //B_ACTIVITY에서 넘겨진 resultCode가 OK일때만 실행
intent.getExtras.getInt("data"); //등과 같이 사용할 수 있는데, 여기서 getXXX()안에 들어있는 파라메터는 꾸러미 속 데이터의 이름표라고 보면된다.
}
}
}

[Activity B 사이드]

Bundle extra;
Intent intent;

onCreate(){
...
extra = new Bundle();
intent = new Intent(); //초기화 깜빡 했다간 NullPointerException이라는 짜증나는 놈이랑 대면하게 된다.
...

extra.putInt("data", 1);
intent.putExtras(extra);
this.setResult(RESULT_OK, intent); // 성공했다는 결과값을 보내면서 데이터 꾸러미를 지고 있는 intent를 함께 전달한다.
this.finish();
}


이와 같이 하면 B 사이드에서 1이라는 데이터값을 가지고 있는 "data"라는 이름표의 꾸러미는 Activity A로 결과값으로써 전달이 되고, 사용이 될 수 있는 것이다.
나는 Bundle을 이용해서 했는데 번들을 사용하지 않고도 결과값을 보내기 위한 메소드는 있다고 본다. 이거는 API를 보면 쉽게 파악할 수 있다.
또한 int값 말고도 모든 데이터 변수들을 보낼 수 있어, 그것에 대한 걱정은 하지 않아도 된다.

실제로 예를 만들어보고 생각을 해보면서 공부해보면 디버깅의 어려움 없이 코드를 구성할 수 있을 것이다.
안드로이드 개발자들이여 디버깅 free의 그날이 올 때까지 쭉~ 노력합시다.

출처:http://blog.naver.com/PostView.nhn?blogId=hisukdory&logNo=50088038280

2012년 10월 11일 목요일

안드로이드에서 아이폰 사진첩처럼 한장씩 넘어가는 갤러리


아래는 첨부된 소스 실행 동영상입니다. 보면 한장씩 자연스럽게 넘어갑니다. 앱 위에 보면 아이폰의 페이지 컨트롤처럼 보여지는 부분도 있는데, 그거까지 설명하면 글이 길어지니 일단 생략하겠습니다.
 


안드로이드 기본 뷰는 한장씩 넘어가는 게 안되서 ViewFlipper나 ViewSwitcher로 구현을 하게 되는데 손으로 드래그해서 스크롤되는 효과를 줄 수가 없게 되죠. 여기서는 Gallery뷰를 상속받아 스크롤이 미끄러질때 미끄러지는 속도를 딱 한장만 넘어갈 정도의 속도로 낮춰서 만들었습니다.

학창시절 물리시간에 배운 기억을 더듬어봐서 속도를 공식으로 표현하면 속도의 제곱은 2 * 마찰계수 * 중력가속도 * 이동거리가 됩니다. 물리에 관한 글이 아니므로 어떻게 유도했는지 자세한 설명은 생략하겠습니다.


일단 상수인 마찰계수와 중력가속도부터 구해야하는데 정보가 없으니 안드로이드 프레임워크 소스를 열어서 분석해봐야합니다. 실제로 저런식으로 구현됐는지도 확인해봐야하고요. 설마했는데 실제로 소스를 열어보니 실제로 지구의 중력가속도에 실제 픽셀을 거리로 변환해서 구현했습니다. (이렇게 구현한 것도 사실 이해가 안가는데 토성, 목성 같은 곳의 중력가속도 정보도 있습니다;;;)

public OneFlingGallery(Context context, AttributeSet attrs) {
super(context, attrs);
float ppi = context.getResources().getDisplayMetrics().density * 160.0f;
mDeceleration = SensorManager.GRAVITY_EARTH // g (m/s^2)
* 39.37f // inch/meter
* ppi // pixels per inch
* ViewConfiguration.getScrollFriction();
    }

중력가속도는 SensorManager.GRAVITY_EARTH * 39.37f * ppi
마찰계수는 ViewConfiguration.getScrollFriction() 입니다.
소스를 열어보면 마찰계수가 0.015로 되어있습니다. 저 마찰계수는 빙판과 아이스하키 공(퍽)의 마찰계수 수준입니다. 스크롤을 한번하면 멈추지 않고 쭉쭉 미끄러지는 게 이해가 가는군요. (기껏 물리학적으로 자연스럽게 구현해서 저런식으로 부자연스럽게 낮은 마찰계수를 적용했는지는 이해가 안갑니다만)

참고로 진저나 프로요 이하에서는 마찰계수가 프레임워크에 상수로 고정되어 있으나 API 11(3.0 허니콤) 부터는 setFriction메소드로 마찰계수의 변경이 가능합니다. 그때부터는 속도가 아니라 마찰계수를 바꾸면 되니 더 자연스럽게 구현을 할 수 있을 겁니다.

이제 이동거리를 구해봅시다. 갤러리같은 어댑터뷰는 사실 스크롤이 되는 게 아니라 자식뷰들을 반대쪽으로 이동시키는 구조로 되어 있습니다. getScrollX()로 구하면 항상 0이 나오기 때문에 onScroll을 오버로드해서 자식뷰들을 이동시킨 거리를 합해서 구해야합니다.

    @Override
    public boolean onScroll(MotionEvent e1, MotionEvent e2, floatdistanceX,
    float distanceY) {
mDistanceX += distanceX;
return super.onScroll(e1, e2, distanceX, distanceY);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
    mDistanceX = 0;
}
return super.onTouchEvent(event);
    }
 
이렇게 하면 mDistanceX에 스크롤된 거리가 합산이 됩니다. onScroll을 오버로드 하지말고, onTouchEvent에서 이벤트거리만 합산해도 되지 않겠냐고 생각할지 모르지만 안됩니다. 마지막에 onScroll이벤트보다 fling이 먼저 발생하는데 그 때 1,2 픽셀정도 오차가 나게되고 한장씩 넘어가는 건 맞지만 딱 맞아떨어져서 멈추지 않기 때문에 묘하게 거슬립니다.

갤러리 크기에서 스크롤된 거리를 빼면 이동해야할 거리가 나옵니다.

    @Override
    public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
    float velocityY) {

float toMoveDistance = getWidth() - Math.abs(mDistanceX);
float maxVelocity = (float) Math.sqrt(toMoveDistance *mDeceleration
* 2);
float revisedVelocityX = 0;

if (velocityX > 0) {
    revisedVelocityX = Math.min(velocityX, maxVelocity);
} else {
    revisedVelocityX = Math.max(velocityX, -maxVelocity);
}

return super.onFling(e1, e2, revisedVelocityX, velocityY);
    } 

이렇게 구한 이동해야할 거리로 속도를 구합니다. 속도를 자연스럽게 구현할 때 속도를 낮추는 건 괜찮지만 빠르게 하는 건 이상하겠죠? 자연스럽게 하기위해 속도는 원래 속도와 이동해야할 속도 중 낮은 속도로 수정해줍니다. 이렇게 해서 가다가 멈춰도 알아서 적당한 위치로 붙기 때문에 속도를 낮춰도 괜찮습니다. 그리고 그렇게 구한 속도를 fling에 넣어주면 됩니다.

아래의 전체소스이용해서 갤러리를 만드시면 되고 사용법은 기존의 Gallery 뷰와 동일합니다.

전체소스 보기

첨부된 파일은 동영상처럼 아이폰의 페이지 컨트롤도 포함시켰고, 바로 전에 썼던 메모리관리 기법도 적용한 예제소스입니다. (이 부분도 설명할 부분이 있는데, 너무 방대해져서 그냥 소스만 첨부합니다. 나중에 기회되면 어댑터 내에서 메모리 관리하는 것도 설명할게요)

화면에 꽉차는 이미지 등장할 때부터 부터는 이미지가 좀 늦게 등장하는 현상이 있는데 그림파일 I/O 때문에 발생합니다. 다음에 I/O시간 때문에 UI가 멈추는 시간을 제거하는 기법을 설명하거나 갤러리 안에서 자연스럽게 세로스크롤 되는 기법을 설명하겠습니다.(첨부된 소스 안에 OneFlingScrollGallery는 세로스크롤 하는 갤러리 소스입니다)

출처: http://givenjazz.tistory.com/49

2012년 10월 9일 화요일

일반 문자열 String -> MD5 변환 함수 입니다.


일반 문자열 String -> MD5 변환 함수 입니다.

public String md5(String s) {
    try {
        // Create MD5 Hash
        MessageDigest digest = java.security.MessageDigest.getInstance("MD5");
        digest.update(s.getBytes());
        byte messageDigest[] = digest.digest();

        // Create Hex String
        StringBuffer hexString = new StringBuffer();
        for (int i=0; i<messageDigest.length; i++)
            hexString.append(Integer.toHexString(0xFF & messageDigest[i]));
        return hexString.toString();

    } catch (NoSuchAlgorithmException e) {
        e.printStackTrace();
    }
    return "";
}