Search

'안드로이드'에 해당되는 글 8건

  1. 2011.07.03 세로스크롤이 되는 갤러리
  2. 2011.06.11 어댑터 안에서 메모리 관리하기. (4)

세로스크롤이 되는 갤러리

Android 2011.07.03 13:23 Posted by 기분째즈
가로스크롤로 그림은 넘기면서 안에 있는 내용물은 세로스크롤이 되는 갤러리입니다. 그림을 넘기는 부분까지 세로스크롤을 원한다면 Gallery를 상속받을 게 아니라 ListView를 상속받아서 사용해야합니다. Gallery는 가로스크롤 밖에 되지 않습니다.

동영상을 보면 이해가 가실 겁니다.
 


간단히 해결될 문제처럼 보이지만 그리 간단한 문제가 아닙니다. 그냥 getView에 이미지를 보여주는 게 아니라 스크롤뷰로 한번싸면 될 거 같지만, 그러면 스크롤뷰가 이벤트를 다 가져가 버려서 가로스크롤이 되지 않습니다. dispatchTouchEvent를 쓰면 둘 다 작동은 되지만 손가락이 일직선으로 좌우로 움직이는 게 아니기 때문에 의도한대로 스크롤 되는 게 아니라 정말 손가락 움직이는대로 움직이게 됩니다. 동영상처럼 세로스크롤만 하려고 했는데 오른쪽 왼쪽에 있는 그림들이 들어갔다 나왔다 거리죠.

onTouchEvent를 상속받아서 손가락이 가로로 움직이면 갤러리에서 처리하고 세로로 움직이면 스크롤뷰에서 처리하면 될 거 같지만, 인간의 손가락은 그렇게 정확하게 움직여주지 않습니다. 가로로 움직이려고 했는데,  첫 터치는 세로로 움직일 수도 있거든요...

한가지 방법은 갤러리에서 터치이벤트를 모두 낚아채서 첫터치 이후 일정거리(예제에서는 1/15인치)를 가로로 움직이면 가로 스크롤, 세로로 움직이면 세로스크롤로 처리하는 방법이 있습니다. 주의사항은 세로스크롤로 보낼 때는 첫 이벤트인 것처럼 위장하기 위해서 마우스이벤트를 DOWN이벤트로 수정해줘야합니다.

# 한장씩 넘어가는 갤러리 
# 어댑터안에서 메모리 관리하기
에서 중요한 소스는 설명했기 때문에 그냥 전체소스만 붙이겠습니다. 말로 하는 것보다 소스를 보시는 게 더 이해가 빠를 수 있으니까요.

OneFlingScrollGallery.java

package com.givenjazz.android;


import android.content.Context;

import android.hardware.SensorManager;

import android.util.AttributeSet;

import android.view.MotionEvent;

import android.view.ViewConfiguration;

import android.widget.Gallery;


public class OneFlingScrollGallery extends Gallery {

    private static final int NOTHING = 0;

    private static final int HORIZONTAL = 1;

    private static final int VERTICAL = 2;


    private float mSensitivity;

    

    private float mDownX;

    private float mDownY;

    private boolean mNeedToPosition;

    private boolean mNeedToJudge;

    private int mDirection;


    private float mDistanceX;

    private float mDeceleration;


    public OneFlingScrollGallery(Context context) {

        this(context, null);

    }


    public OneFlingScrollGallery(Context context, AttributeSet attrs) {

        super(context, attrs);

        float ppi = context.getResources().getDisplayMetrics().density * 160.0f;

        mSensitivity = ppi/15; // 민감도 1/15인치

        mDeceleration = SensorManager.GRAVITY_EARTH // g (m/s^2)

                * 39.37f // inch/meter

                * ppi // pixels per inch

                * ViewConfiguration.getScrollFriction();

    }


    @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);

    }


    @Override

    public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {


        if (mNeedToPosition) {

            mDistanceX = 0;

            mNeedToPosition = false;

            distanceX = 0;

        }

        mDistanceX += distanceX;

        return super.onScroll(e1, e2, distanceX, distanceY);

    }


    @Override

    public boolean onTouchEvent(MotionEvent e) {

        switch (mDirection) {

            case HORIZONTAL:

                return super.onTouchEvent(e);

            case VERTICAL:

                if (mNeedToJudge == true) {

                    mNeedToJudge = false;

                    //스크롤뷰에서 처음 받을 이벤트이니 다운이벤트로 위장

                    e.setAction(MotionEvent.ACTION_DOWN);

                }


                //스크롤뷰로 이벤트 전달

                getSelectedView().onTouchEvent(e);

                return true;

            case NOTHING:

                float deltaX = Math.abs(e.getX() - mDownX);

                float deltaY = Math.abs(e.getY() - mDownY);

                if (deltaX > deltaY + mSensitivity)

                    mDirection = HORIZONTAL;

                else if (deltaX + mSensitivity < deltaY)

                    mDirection = VERTICAL;


        }

        return true;

    }


    @Override

    public boolean onInterceptTouchEvent(MotionEvent e) {

        mDirection = NOTHING;

        mNeedToPosition = true;

        mNeedToJudge = true;

        mDownX = e.getX();

        mDownY = e.getY();

        return true;

    }


}



ImageAdapter.java (소스의 간결함을 위해 convertView 사용은 생략) 

package com.givenjazz.android;


import java.lang.ref.WeakReference;

import java.util.ArrayList;

import java.util.List;


import android.content.Context;

import android.util.Log;

import android.view.View;

import android.view.ViewGroup;

import android.widget.BaseAdapter;

import android.widget.Gallery;

import android.widget.ImageView;

import android.widget.ScrollView;


public class ImageScrollAdapter extends BaseAdapter {

    private List<Integer> mResources;

    private Context mContext;

    private List<WeakReference<View>> mRecycleList = new ArrayList<WeakReference<View>>();


    public ImageScrollAdapter(Context c, List<Integer> resources) {

mContext = c;

mResources = resources;

    }


    @Override

    public int getCount() {

return mResources.size();

    }


    @Override

    public Object getItem(int position) {

return mResources.get(position);

    }


    @Override

    public long getItemId(int position) {

return position;

    }


    public void recycle() {

RecycleUtils.recursiveRecycle(mRecycleList);

    }


    public void recycleHalf() {

int halfSize = mRecycleList.size() / 2;

List<WeakReference<View>> recycleHalfList = mRecycleList.subList(0,

halfSize);

RecycleUtils.recursiveRecycle(recycleHalfList);

for (int i = 0; i < halfSize; i++)

    mRecycleList.remove(0);

    }


    @Override

    public View getView(int position, View convertView, ViewGroup parent) {

ScrollView scrollView = new ScrollView(mContext);

ImageView i = new ImageView(mContext);


try {

    i.setImageResource(mResources.get(position));

} catch (OutOfMemoryError e) {

    if (mRecycleList.size() <= parent.getChildCount()) {

Log.e(this + "", "size:" + mRecycleList.size());

throw e;

    }

    Log.w(this + "", e.toString());

    recycleHalf();

    System.gc();

    return getView(position, scrollView, parent);

}

i.setAdjustViewBounds(true);

i.setLayoutParams(new Gallery.LayoutParams(i.getDrawable()

.getIntrinsicWidth(), i.getDrawable().getIntrinsicHeight()));


scrollView.addView(i);


mRecycleList.add(new WeakReference<View>(scrollView));

return scrollView;

    }

}



라이센스는 언제나 그렇듯이 아파치2.0으로 공개합니다.

신고

어댑터 안에서 메모리 관리하기.

Android 2011.06.11 21:56 Posted by 기분째즈

얼마 전에 내 블로그에 안드로이드 메모리 누수 줄이기 포스팅을 했는데, 어댑터 뷰에서 관리하는 법을 예제와 곁들어 추가로 설명할까 합니다.

어댑터뷰 안에 데이터는 실질적으로 어댑터가 관리를 하기 되기에 메모리 관리하는 부분도 어댑터 안에서 관리하는 게 좀 더 수월합니다. 가비지 콜렉터가 어느정도 알아서 관리를 해주나, 이미지처럼 메모리를 많이 사용할 경우 빠르게 해제하기 위해 직접관리를 해야하는 경우도 생깁니다.

다음 동영상은 저번에 올렸던 한 장씩 넘어가는 갤러리뷰에다가 고용량의 이미지 파일을 넣어서 메모리 관리가 되고 있는 모습입니다. (예를 들기 위해 오버했습니다. 실제로는 당연히 모바일 환경에 맞게 이미지를 줄여서 써야겠죠. 이미지 읽어들이는 시간도 꽤 길어서 멈추는 느낌이 드는데 이거 해결하는 기법은 포스팅하겠습니다) 바탕화면급 고용량 이미지라 빠르게 3~4장 정도만 이미지를 읽어도 java.lang.OutOfMemoryError: bitmap size exceeds VM budget이 발생합니다. 동영상보면 빨간색과 주황색 로그가 올라가는데 그게 Error가 발생한 부분이고 동영상은 메모리를 해제해주고 계속 작동 시키는  모습을 볼 수 있습니다.



어댑터에서 구현한 부분은 다음 부분입니다.


    //액티비티에서 어댑터를 메모리에서 해제하기 위해쓰는 메소드.
    //어댑터를 사용하는 액티비티의 onDestroy에 넣어주면 된다
.

    public void recycle() {

RecycleUtils.recursiveRecycle(mRecycleList);

    }


    //만들었던 뷰 목록 중 반을 지우는 메소드 

    public void recycleHalf() {

int halfSize = mRecycleList.size() / 2;

List<WeakReference<View>> recycleHalfList = mRecycleList.subList(0,

halfSize);

RecycleUtils.recursiveRecycle(recycleHalfList);

for (int i = 0; i < halfSize; i++)

    mRecycleList.remove(0);

    }



여기 RecycleUtils.recursiveRecycle은 전에 올렸던 메모리 누수 관리하기를 참고해주세요.

RecycleUtils 소스보기


WeakReference로는 레퍼런스를 해도 가비지 콜렉터할 때 신경을 안쓰기 때문에 가비지콜렉팅 대상에 포함됩니다. 예를 들어 Reference가 아니라 그냥 List에 View를 포함했다면 어댑터에서 자동으로 메모리 해제를 안하기 때문에 쉽게 메모리 오류가 발생할 겁니다.

    @Override

    public View getView(int position, View convertView, ViewGroup parent) {

ImageView i = new ImageView(mContext);


try {

    i.setImageResource(mResources.get(position));

} catch (OutOfMemoryError e) {

    if (mRecycleList.size() <= parent.getChildCount()) {

Log.e(this + "", "size:" + mRecycleList.size());

throw e;

    }

    Log.w(this + "", e.toString());

    recycleHalf();

    System.gc();

    return getView(position, convertView, parent);

}

i.setAdjustViewBounds(true);

i.setLayoutParams(new Gallery.LayoutParams(LayoutParams.MATCH_PARENT,

LayoutParams.MATCH_PARENT));

mRecycleList.add(new WeakReference<View>(i));

return i;




 이미지를 읽다가 OutOfMemoryError 예외가 발생하면 전에 어댑터에서 사용했던 뷰의 반을 메모리에서 해제하고, 재귀로 getView를 호출합니다. 오류가 안 날때까지 반씩 줄이다가 어댑터뷰의 크기만큼 줄였는데도 메모리오류가 나면 그냥 오류던지고 종료시키도록 구현했는데, 이 부분은 상황에 맞게 처리해주시면 되겠죠. 이건 그냥 메모리를 관리하는 하나의 예제일 뿐입니다. OutOfMemoryError가 자주 난다면 try/catch로 잡는 것보다 mRecycleList의 크기를 비교해서 미리 해제해주는 등의 더 섬세하게 관리를 해주셔야 될겁니다.

갤러리는 가로스크롤 되고 안에 있는 내용이 세로스크롤 되는 뷰는 다음에 설명하도록 하겠습니다. 완전히 세로로 스크롤 되는 건 원하시면 그냥 리스트뷰 쓰시면 되겠습니다.

ImageAdapter 소스 전체보기


소스는 이미지 리소스만 다르고 전에 올렸던 안드로이드 아이폰처럼 한장씩 넘기를 갤러리와 동일해서 생략하겠습니다.
신고


 

티스토리 툴바