블로그 이미지
매몰

모바일 어플리케이션 개발 1인 기업 고영진모바일입니다. 저와 함께 맛깔스러운 앱을 만들어 보아요~

Rss feed Tistory
개발/android 2019.01.17 17:27

EditText에 자동으로 단위 콤마 넣기



NumberFormat 을 이용하면 숫자에 단위 콤마를 쉽게 찍을수 있다.


하지만 이를 EditText에 적용할려고 하면 한가지 문제가 생긴다.


입력할때마다 콤마가 찍혀야 하는데...

addTextChangedListener에서 NumberFormat으로 콤마를 찍으면 당연히 텍스트가 변경되므로 다시 리스너가 호출된다. 즉, 호출이 무한 반복되면서 앱이 멈추게 된다.


그래서 살짝 꼼수를 부려봤다.


EditText 밑에 같은 크기의 TextView를 깔고 여기에 콤마를 찍은 텍스트를 대신 써주고,

후에 EditText를 동기화 하는 것이다. 물론, EditText는 투명하게 설정해 안보이게 한다.


사실 임시 방편으로 만든것인데.. 생각보다 잘 작동해 계속 쓰고 있다 ㅎㅎㅎ



소스는 다음과 같다. 요즘 뜨고 있다는 코틀린으로 작성했다~


class NumberEditText: RelativeLayout {

private var editText: EditText
private var textView: TextView

init {
editText = EditText(context)
textView = TextView(context)

initBase()
}

constructor(context: Context): super(context) {
editText = EditText(context)
textView = TextView(context)

initBase()
}

private fun initBase() {
layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT)

//EditText가 안보이도록 투명 처리한다
editText.setTextColor(Color.TRANSPARENT)
editText.setBackgroundColor(Color.TRANSPARENT)
editText.gravity = gravity

textView.gravity = gravity

//TextView 위에 EditText를 생성한다
addView(textView, LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT))
addView(editText, LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT))

editText.setOnTouchListener { view, motionEvent ->
//EditText를 터치하면 TextView의 내용을 동기화 한다
if (motionEvent.action == MotionEvent.ACTION_UP) {
//내용을 쓸때 입력 커서 위치가 변경되지 않도록 조정한다
val length = textView!!.text.length - (view as EditText).text.length
val selectionStart = view.selectionStart + length
val selectionEnd = view.selectionEnd + length

//내용을 쓰고 커서 위치를 조정한 후 적용되도록 포커스를 요청한다
view.setText(textView?.text)
view.setSelection(selectionStart, selectionEnd)
view.requestFocus()
}

false
}

editText.addTextChangedListener(object : TextWatcher {
override fun afterTextChanged(s: Editable?) {

}

override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {

}

override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
//콤마를 찍어 TextView에 쓴다
textView.text = getNumberText(s.toString())
}
})
}

private fun getNumberText(text: String): String {
return if (text.length > 1) NumberFormat.getNumberInstance().format(text.replace(",", "").toDouble()) else text
}
}



수제앱장인


 

 

CEO

Developer

S/W Enginner

고영진


실패만 하고 있어도 꿈을 포기하지 않는 남자
제가 직접 경험하고 습득한 지식을 위주로 올릴게요

사업자 정보 표시
고영진모바일 | 고영진 | 서울특별시 관악구 낙성대동 서울대연구공원 SK상생혁신센터 | 사업자 등록번호 : 109-11-82076 | TEL : 010-9990-3674 | Mail : gyjmeba@hanmail.net | 통신판매신고번호 : 2010-서울강서-0217호 | 사이버몰의 이용약관 바로가기
개발/android 2018.06.07 14:03

Intent 전달 방식을 모방하여 부모 activity를 호출하기


IOS에서는 prepare()을 통해서 부모 controller를 쉽게 전달할 수 있다.

하지만 Android에서는 Intent로 activity를 전달하기 쉽지 않다.


뭐, Intent 말고도 여러 전달 방식이 있기 때문에 상관없지만...

깔끔하게 코딩하고 싶은 마음에 함 만들어 보았다.


static을 이용하지만, 마치 Intent로 전달하는것 같은 느낌으로ㅎㅎ



public class BaseActivity extends FragmentActivity {

private static final String KEY_PARAM_CONTEXT_ID = "ParamContextId";

//전달할 Context를 담는 Map
private static HashMap<Long, Context> mParamContextMap = null;

//전달된 Context를 저장할 인스턴스
private Context mParamContext;

//부모 Activity에서 호출되어 Context를 받는다
public static void putParamContext(Intent intent, Context context) {

//Map이 널이면 즉 Context가 하나도 없다면 생성한다
if (mParamContextMap == null)
mParamContextMap = new HashMap<>();

//현재 시간을 식별자로 전달 Intent에 저장한다. 이는 Context가 static에 담아지기 때문에 상속받은 모든 activity에서 만약에 있을 출동을 막기 위함이다.
long id = System.currentTimeMillis();
intent.putExtra(KEY_PARAM_CONTEXT_ID, id);

//위의 식별자를 해당 키로 하여 Map에 Context를 담는다.
mParamContextMap.put(id, context);
}

//부모 Activity의 Context를 반환한다.
protected Context getParamContext() {
return mParamContext;
}

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

//전달된 Context가 있다면 실행한다.
if (mParamContextMap != null) {
long id = getIntent().getLongExtra(KEY_PARAM_CONTEXT_ID, 0);
if (id != 0) {
//Context를 인스턴스에 저장하고 static Map에서는 삭제한다.
mParamContext = mParamContextMap.get(id);
mParamContextMap.remove(id);
}

//Map에 아무것도 없다면 널로 풀어준다.
if (mParamContextMap.size() == 0)
mParamContextMap = null;
}
}
}




부모 Activity에서 자식 Activity 호출...


Intent intent = new Intent(this, MyActivity.class);
BaseActivity.putParamContext(intent, this);
startActivity(intent);



자식 Activity에서 부모 Activity 호출...


public class MyActivity extends BaseActivity {

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

    Context parentContext = getParamContext();

//부모 Context롤 이용하여 작업한다.

//

//

}

}


 

 



수제 앱 장인: 고영진


(주)고영진모바일

1인기업 대표이사 겸 개발자

  

     실패만 하고 있어도 꿈을 포기하지 않는 남자 

     제가 직접 경험하고 습득한 지식을 위주로 올릴게요

 






사업자 정보 표시
고영진모바일 | 고영진 | 서울특별시 관악구 낙성대동 서울대연구공원 SK상생혁신센터 | 사업자 등록번호 : 109-11-82076 | TEL : 010-9990-3674 | Mail : gyjmeba@hanmail.net | 통신판매신고번호 : 2010-서울강서-0217호 | 사이버몰의 이용약관 바로가기
개발/android 2017.09.11 10:17

ListView에 EditText를 넣을때 반드시 살펴봐야 할점

설정 페이지를 만들때 ListView로 EditText를 넣고 싶은 유혹에 빠진다


하지만 ListView의 특성상 사실 EditText는 적합하지 않다.

ListView는 각각의 row뷰를 재사용하여 메모리를 아끼기 때문에 

EditText의 변경값을 일일이 저장해 두었다가 다시 불려줘야 한다.


그리고 이때 진짜 주의할게 하나 있다.

변경 리스너의 중복을 방지하는 것이다. 반드시 재사용 뷰의 예전 리스너를 지워줘야 한다.

안그러면 예전 목록들과 뒤죽박죽 되버린다.



이해를 돕기 위해 리스너를 지우지 않았을때와 지웠을때도 비교해 보았다.


public class MainActivity extends Activity {

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);

//리스트 목록 넣기
String[] names = getResources().getStringArray(R.array.list_item);
ArrayList<Item> list = new ArrayList<>();

int length = names.length;
for (int i = 0 ; i < length ; i++)
list.add(new Item(names[i]));

((ListView)findViewById(R.id.ItemListView)).setAdapter(new ItemAdapter(this, R.layout.list_row, list));
}

//리스트 목록 클래스
private class Item {
public String mName, mValue;
public TextWatcher mTextWatcher;

public Item(String name) {
mName = name;
mValue = "";

//EditText 변경 리스너 생성
mTextWatcher = new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {

}

@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
//변경된 값을 저장한다
mValue = s.toString();
}

@Override
public void afterTextChanged(Editable s) {

}
};
}
}

private class ItemAdapter extends ArrayAdapter<Item> {

private int mResource;

public ItemAdapter(Context context, int resource, List<Item> list) {
super(context, resource, list);
mResource = resource;
}

@Override
public View getView(int position, View convertView, ViewGroup parent) {
LinearLayout itemView;

if (convertView == null) {
itemView = new LinearLayout(getContext());

LayoutInflater vi = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
vi.inflate(mResource, itemView, true);
}
else
itemView = (LinearLayout) convertView;

Item item = getItem(position);

((TextView)itemView.findViewById(R.id.NameTextView)).setText(item.mName);

EditText editText = (EditText)itemView.findViewById(R.id.ValueEditText);

//예전 리스너를 삭제한다
clearTextChangedListener(editText);

//값을 불려오고 해당 리스너를 적용한다
editText.setText(item.mValue);
editText.addTextChangedListener(item.mTextWatcher);

return itemView;
}

private void clearTextChangedListener(EditText editText) {
//리스트 목록의 모든 리스너를 대상으로 검사하여 삭제해 준다
int count = getCount();
for (int i = 0 ; i < count ; i++)
editText.removeTextChangedListener(getItem(i).mTextWatcher);
}
}
}



main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent" android:layout_height="match_parent"
android:gravity="center_vertical">

<ListView android:id="@+id/ItemListView"
android:layout_width="match_parent" android:layout_height="match_parent"/>

</LinearLayout>



list_row.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent" android:layout_height="match_parent"
android:padding="10dp" android:gravity="center_vertical">

<TextView android:id="@+id/NameTextView"
android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1"
android:layout_marginRight="5dp"/>

<EditText android:id="@+id/ValueEditText"
android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="2"/>

</LinearLayout>



array.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>

<string-array name="list_item">
<item>사자</item>
<item>호랑이</item>
<item>토끼</item>
<item>코끼리</item>
<item>강아지</item>
<item>고양이</item>
<item>표범</item>
<item>송아지</item>
<item>돼지</item>
<item>코알라</item>
<item>캥거루</item>
<item>양</item>
<item>염소</item>
<item>늑대</item>
<item>여우</item>
<item>닭</item>
<item>독수리</item>
<item>쥐</item>
<item>뱀</item>
<item>말</item>
<item>곰</item>
<item>두더지</item>
<item>사슴</item>
<item>노루</item>
<item>하마</item>
<item>악어</item>
<item>꿩</item>
<item>비둘기</item>
<item>까마귀</item>
<item>고슴도치</item>
</string-array>

</resources>




clearTextChangedListener() 함수를 주석처리 했을때 (예전 변경 리스너를 지워주지 않았을때)






clearTextChangedListener() 함수를 주석처리 하지 않았을때  (예전 변경 리스너를 지워주었을때)






 

 



수제 앱 장인: 고영진


고영진모바일 1인기업 대표 겸 개발자 

  

     실패만 하고 있어도 꿈을 포기하지 않는 남자 

     제가 직접 경험하고 습득한 지식을 위주로 올릴게요

 


사업자 정보 표시
고영진모바일 | 고영진 | 서울특별시 관악구 낙성대동 서울대연구공원 SK상생혁신센터 | 사업자 등록번호 : 109-11-82076 | TEL : 010-9990-3674 | Mail : gyjmeba@hanmail.net | 통신판매신고번호 : 2010-서울강서-0217호 | 사이버몰의 이용약관 바로가기
개발/android 2016.03.28 10:19

버튼의 Selector 이미지를 간절히 가져오고 싶다면?



보통 Selector를 xml로 아래와 같이 만들어 놓는다.

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="false" android:drawable="@drawable/btn" />
<item android:state_pressed="true" android:drawable="@drawable/btn_press" />
</selector>



그리고 버튼 background에 적용한다.

<Button android:id="@+id/Button" android:text="@string/btn"
android:layout_width="100dp" android:layout_height="100p"

android:background="@drawable/btn_selector"/>




별로~ 그럴일은 없지만 만약 이 selector의 android:drawable 이미지를 자바 코드상으로 불려오고 싶다면??


private Drawable[] getPressImages(View v) {
//Selector 이미지를 StateListDrawable 객체로 불러옴
StateListDrawable statedrawable = (StateListDrawable)v.getBackground();
if(statedrawable != null) {
try {
//Veiw의 현재 이미지 상태를 배열에 가져옴
int[] currentstate = v.getDrawableState();

//이미지를 불러오기 위해 인덱스와 이미지 메소드를 가져옴
Method indexmethod = StateListDrawable.class.getMethod("getStateDrawableIndex", int[].class);
Method drawblemethod = StateListDrawable.class.getMethod("getStateDrawable", int.class);

//인덱스 메소드와 상태 배열을 이용해 StateListDrawable의 리스트 인덱스를 가져옴
int index = (int) indexmethod.invoke(statedrawable, currentstate);

//얻어낸 리스트 인덱스로 StateListDrawable에서 이미지를 각각 불러옴
Drawable drawable1 = (Drawable) drawblemethod.invoke(statedrawable, index);
Drawable drawable2 = (Drawable) drawblemethod.invoke(statedrawable, index + 1);

return new Drawable[] {drawable1, drawable2};

} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}

return null;
}


 

 



수제 앱 장인: 고영진


고영진모바일 1인기업 대표 겸 개발자 

  

     실패만 하고 있어도 꿈을 포기하지 않는 남자 

     제가 직접 경험하고 습득한 지식을 위주로 올릴게요

 




사업자 정보 표시
고영진모바일 | 고영진 | 서울특별시 관악구 낙성대동 서울대연구공원 SK상생혁신센터 | 사업자 등록번호 : 109-11-82076 | TEL : 010-9990-3674 | Mail : gyjmeba@hanmail.net | 통신판매신고번호 : 2010-서울강서-0217호 | 사이버몰의 이용약관 바로가기
개발/android 2015.10.26 13:24

이미지를 싹뚝 자를때 꼭 알아야 할 비밀

이미지를 전체가 아닌 일부분만 출력하고 싶을때 사용하는 기법이 Image Clipping 이다.

게임을 만들때는 없어서는 안되는 아주 고마운 녀석이다.


보통 Clipper 객체를 만들어 사용하는데...

어렸을때는 왜 굳이 객체까지 만들어서 이미지를 자르나.. 그냥 자르면 되지..

라며 쓸데 없는것이라고 생각했었다.


하지만 어른이 되고 개발을 업으로 살다보니 조상님들의 지혜에 감탄하듯 

그 이유를 깨닫게 되었다ㅎㅎ


아주 단순하고 지극히 당연하지만... 그래서 간과하기 쉬운 클리핑의 원리!

비밀 아닌 비밀같은 클리핑을 알아보자~


클리핑 좌표



위 그림처럼 클리핑은 이미지 원본크기와 출력크기가 다르다는 것이 문제이다.

또 원본좌표는 (0, 0)에서 시작되지만 출력좌료는 스크린좌표가 더해진다.


이것을 같은 크기와 좌표로 작업할 수 있도록 해주는것이 클리핑 객체의 역활이다.


즉, 이미지마다 모두 크기가 다를수 있기 때문에 이미지 하나당 클리핑 객체도 하나씩 두면 편리하게 사용할 수 있다.


자 이제 코드로 옮겨 보자~



public class Clipper {

public Rect mRect, mOutRect;
private int mImageWidth, mImageHeight;

public Clipper(Bitmap image, Rect outrect) {
if (image != null && !image.isRecycled()) {
//이미지 크기
mImageWidth = image.getWidth();
mImageHeight = image.getHeight();
}

//출력 좌표
mOutRect = outrect;

//클리핑 좌표
mRect = new Rect(0, 0, mImageWidth, mImageHeight);
}

//원본 좌표를 기준으로 자르기
public void setRect(float left, float top, float right, float bottom) {
//이미지 크기와 출력 좌표의 크기 비율을 계산
float widthrate = mImageWidth / (float)mOutRect.width();
float heightrate = mImageHeight / (float)mOutRect.height();

//계산된 비율로 클리핑 좌표 설정
mRect.set((int)(left * widthrate), (int)(top * heightrate), (int)(right * widthrate), (int)(bottom * heightrate));
}

//출력 좌표를 기준으로 자르기
public void setRectForOut(float left, float top, float right, float bottom) {
//출력 좌표
float x = mOutRect.left;
float y = mOutRect.top;

//원본 좌표에 출력 좌표를 적용
setRect(left - x, top - y, right - x, bottom - y);
}
}



클리퍼 클래스가 만들어졌으니 객체에 비밀을 숨긴 채 사용을 해보자~


Bitmap bitmap = BitmapFactory.decodeResource(getContext().getResources(), R.drawable.kkwalla);

//좌표는 출력좌표 기준으로~ 원본좌표를 신경쓸 필요가 없다~

Rect rect = new Rect(200, 500, 400, 700);
Clipper clipper = new Clipper(bitmap, rect);

clipper.setRectForOut(300, 500, 400, 600);

canvas.drawBitmap(bitmap, clipper.mRect, rect, null);




 

 



수제 앱 장인: 고영진


고영진모바일 1인기업 대표 겸 개발자 

  

     실패만 하고 있어도 꿈을 포기하지 않는 남자 

     제가 직접 경험하고 습득한 지식을 위주로 올릴게요

 





사업자 정보 표시
고영진모바일 | 고영진 | 서울특별시 관악구 낙성대동 서울대연구공원 SK상생혁신센터 | 사업자 등록번호 : 109-11-82076 | TEL : 010-9990-3674 | Mail : gyjmeba@hanmail.net | 통신판매신고번호 : 2010-서울강서-0217호 | 사이버몰의 이용약관 바로가기
개발/android 2015.06.22 10:36

Fragment에서 startActivityForResult() 호출시 onActivityResult() requestCode 문제




Fragment에서 startActivityForResult()를 호출했을때

onActivityResult()의 requestCode가 잘못 올 때가 있다.


즉, 분명 requestCode를 100으로 호출했는데 65640와 같은 이상한 값이 나오는 경우이다.


해결방법은 간단하다.

startActivityForResult()를 getActivity().startActivityForResult()로 바꾸면 된다.


아마도 Fragment에서 직접 호출하면 부모액티비티로 값이 전달되지 않는듯 하다.




 

 



수제 앱 장인: 고영진


고영진모바일 1인기업 대표 겸 개발자 

  

     실패만 하고 있어도 꿈을 포기하지 않는 남자 

     제가 직접 경험하고 습득한 지식을 위주로 올릴게요

 





사업자 정보 표시
고영진모바일 | 고영진 | 서울특별시 관악구 낙성대동 서울대연구공원 SK상생혁신센터 | 사업자 등록번호 : 109-11-82076 | TEL : 010-9990-3674 | Mail : gyjmeba@hanmail.net | 통신판매신고번호 : 2010-서울강서-0217호 | 사이버몰의 이용약관 바로가기
개발/android 2013.11.25 10:08

스레드에서 View 변경시 Only the original thread…. 에러 대처법

게임을 만들다 보면 스레드를 자주 이용하게 된다. 

그러다보면 Only the original thread that created a view hierarchy can touch its views 라는 에러를 맞다들이게 되는 경우가 반드시 찾아온다...


이 에러는 말그래로 오리지널 스레드... 즉, 앱 실행시 자동으로 생성되는 메인 스레드외에 직접 만든 스레드로 뷰위젯등을 컨트롤할려고 할때 발생한다... 예를 들어 TextView의 setText()같은 것이다...


나도 처음 게임을 만들 당시 이것 때문에 몇일을 고민했던 적이 있었다... 그러다 찾아낸것이 View의 Post()였는데 이방법은 특정사항에서는 작동이 안되는 문제가 있었다... 

결국, 우연한 기회에 Handler 방식을 알게되었는데 역시 다른 개발자분들은 널리 사용하고 계셨다 ㅎㅎ 아~ 또 나만 삽질끝에 알아낸것이다ㅎㅎ


어잿든 아래와 같이 나름 ActionHandler로 정리해서 유용하게 사용하고 있다





private static final String ACTION_KEY_TYPE = "ActionKeyType";

private static final String ACTION_KEY_VALUE = "ActionKeyValue";

private static final int ACTION_TYPE_SETTEXT = 0;

private static final int ACTION_TYPE_SETSCROLL = 1;


//스레드 영역에서 호출 예 

private void testAction() {

sendActionMsg(ACTION_TYPE_SETTEXT"테스트");

sendActionMsg(ACTION_TYPE_SETSCROLL, 10);

}


//핸들러 호출 함수

private void sendActionMsg(int action, String value) {

Message msg = mActionHandler.obtainMessage();

Bundle bundle = new Bundle();

bundle.putInt(ACTION_KEY_TYPE, action);

bundle.putString(ACTION_KEY_VALUE, value);

msg.setData(bundle);

mActionHandler.sendMessage(msg);

}

  

private void sendActionMsg(int action, int value) {

Message msg = mActionHandler.obtainMessage();

Bundle bundle = new Bundle();

bundle.putInt(ACTION_KEY_TYPE, action);

bundle.putInt(ACTION_KEY_VALUE, value);

msg.setData(bundle);

mActionHandler.sendMessage(msg);

}

 

//핸들러 

public Handler mActionHandler = new Handler() {

public void handleMessage(Message msg) {

Bundle data = msg.getData();

switch(data.getInt(ACTION_KEY_TYPE)) {

case ACTION_TYPE_SETTEXT:

  String strvalue = data.getString(ACTION_KEY_VALUE);

mTextView.setText(strvalue);

  

break;    

case ACTION_TYPE_SETSCROLL:

int intvalue = data.getInt(ACTION_KEY_VALUE);

        mLayout.scrollTo(0, intvalue);

  

break;

}

}

};








 

 매몰: 고영진 대표/개발자


 고영진모바일 1인기업의 하나뿐인 사람. 

 그밖에 동반자 노트북,스마트폰 

     실패만 하고 있어도 꿈을 포기하지 않는 남자. 

     제가 직접 경험하고 습득한 지식을 위주로 올릴게요. 







사업자 정보 표시
고영진모바일 | 고영진 | 서울특별시 관악구 낙성대동 서울대연구공원 SK상생혁신센터 | 사업자 등록번호 : 109-11-82076 | TEL : 010-9990-3674 | Mail : gyjmeba@hanmail.net | 통신판매신고번호 : 2010-서울강서-0217호 | 사이버몰의 이용약관 바로가기
개발/android 2013.10.04 10:45

큰 배경이미지 사용시 OutOfMemoryError의 확실한 해결법

안드로이드 앱을 만들때 배경이미지를 넣어야 하는 경우 OutOfMemoryError에 직면하는 경우가 많다.. 


특히 사이즈가 큰 배경이미지 일수록... 자주 발생한다...


즉, OutOfMemoryError는 이름처럼 이미지를 로딩할때 메모리가 부족해서 발생하는것이다...


이럴경우 그원인이 되는 이미지 사이즈 자체를 줄이거나 로딩할때 아래와 같이 사이즈를 줄여서 불러오면 일단 해결은 된다.



 


BitmapFactory.Options options = new BitmapFactory.Options();

options.inSampleSize = 2;


Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.opening, options);





하지만 이방법은 치명적 단점이 있다.


사이즈를 줄이므로 이미지가 선명하지 못하고 깨져서 보이며, 보통 LinearLayout과 같은 배경으로 많이 쓰이는 view에는 setBackgroundDrawable() 뿐이라서 bitmap을 Drawable로 변환해야 하는 번거로움이 있다...


이 두가지 단점을 한번에 해결하는 방법은 과연 없을까?


있다. 나도 항상 위와 같은 방법을 쓰다가 영 마음에 안들어서 여러 테스트를 거쳐 알아냈다...

물론 나중에 안거지만 다른분들 포스팅을 보니 이미 그렇게 사용하고 계시는 분들이 있으셨다..

역시 세상에는 코딩신님들이 많은것 같다ㅎㅎ 난 무한 샆질끝에 알아냈다고 좋아했는데ㅎㅎㅎ


방법은 무지 간단하다...





LinearLayout layout = (LinearLayout)findViewById(R.id.BgLayout);


layout.setBackgroundDrawable(new BitmapDrawable(getResources(), BitmapFactory.decodeResource(getResources(), R.drawable.opening)));





이렇게 로딩을 해서 바로 집어넣고 onDestroy()에서 메모리를 풀어주면 된다...





@Override

public void onDestroy() {

recycleView(findViewById(R.id.BgLayout));

}


private void recycleView(View view) {

if(view != null) {

Drawable bg = view.getBackground();

if(bg != null) {

bg.setCallback(null);

((BitmapDrawable)bg).getBitmap().recycle();

view.setBackgroundDrawable(null);

}

}

    }





참고로


xml에서 android:background="@drawable/opening" 로 바로 넣거나 layout.setBackgroundResource(R.drawable.opening) 또는  getResources().getDrawable(R.drawable.opening) 로 이미지를 가져오면 원본을 그대로 사용하는것이라서 메모리도 많이 잡아먹고 recycle를 하게 되면 다음번 실행때 에러가 난다...


결론은...


반드시 위와 같이 new로 복사본을 새로 생성해서 사용해야 메모리도 적게 먹고 recycle도 할수 있어서 OutOfMemoryError를 예방할 수 있다....





도움이 되셨다면 구독해주세요~ 유용하고 좋은글 많이 올릴게요~ㅎㅎ






 

 매몰: 고영진 대표/개발자


 고영진모바일 1인기업의 하나뿐인 사람. 

 그밖에 동반자 노트북,스마트폰 

     실패만 하고 있어도 꿈을 포기하지 않는 남자. 

     제가 직접 경험하고 습득한 지식을 위주로 올릴게요. 







사업자 정보 표시
고영진모바일 | 고영진 | 서울특별시 관악구 낙성대동 서울대연구공원 SK상생혁신센터 | 사업자 등록번호 : 109-11-82076 | TEL : 010-9990-3674 | Mail : gyjmeba@hanmail.net | 통신판매신고번호 : 2010-서울강서-0217호 | 사이버몰의 이용약관 바로가기
  • yonoo88 2013.12.14 22:56 신고 ADDR 수정/삭제 답글

    오 이 문제 때문에 고생하다가 메니페스트에 android:largeHeap="true" 로 줘서 문제를 해결했었는데 그래도 간간히 아웃오브메모리 에러 신고가 들어와서 어케하나 싶었는데 한번 해봐야겠네요

  • yonoo88 2013.12.16 02:28 신고 ADDR 수정/삭제 답글

    layout.setBackgroundDrawable(new BitmapDrawable(getResources(),

    이 부분에서 setBackgroundDrawable 이게 가운데 줄그어져서 나오는데 어케해야하죠?

  • 건프 2014.02.10 10:32 신고 ADDR 수정/삭제 답글

    몇가지 잘못된게 있군요 일단은 setbackground 는 젤리빈이후부터 사용가능입니다.
    4.0 아샌부터가 아닙니다..
    그리고 저렇게 내부이미지 리소스를 리사이클하게되면 다시 다른곳에서 사용할때
    널포인터 입섹션날겁니다. 내부이미지 리소스는 리사이클하지 않는게 원칙입니다.

    • 대세를 따르지 않고 대세를 만드는 매몰 2014.02.11 10:02 신고 수정/삭제

      좋은지적 감사합니다~
      setbackground는 젤리빈 이후가 맞아요ㅎㅎ
      그리고 context.getResources().getDrawable(R.drawable.leftarrow) 등과 같이 내부리소스를 그대로 사용하는 경우에는 다른곳에서 널포인트가 되지만 본문에서처럼 new BitmapDrawable(getResources(), BitmapFactory.decodeResource(getResources(), R.drawable.opening)) 식으로 내부리소스를 로딩해 복사본을 생성하면 로딩된 복사본만 리사이클되기 때문에 원칙에 어긋나지 않습니다..
      저도 처음에 널포인트를 경험하고 여러 테스트를 거처 찾아낸 방법이에요ㅎㅎ 한번 해보시고 문제가 될시에는 다시 말씀해주세요~

  • 혼마 2015.08.06 13:39 신고 ADDR 수정/삭제 답글

    안녕하세요. 안드로이드 초보 개발자입니다.
    우선 좋은글 감사드립니다. 덕분에 사이즈가 큰 이미지를 배경화면으로 넣는 문제를 해결하였습니다.
    그런데 한가지 질문이 있습니다.
    1.2MB짜리 이미지를 배경으로 심었는데 처음에는 무리없이 잘 돌아갔습니다.
    그런데 앱 개발과정중에 몇번씩 빌드하고 실행하다보니 다른 액티비티가 많이 생긴 후 건프님 말씀처럼 널포인터 익셉션이 일어났습니다.
    소스 일부 첨부하겠습니다.
    LinearLayout loginLayout = (LinearLayout) findViewById(R.id.login_layout);
    loginLayout.setBackground(new BitmapDrawable(getResources(), BitmapFactory.decodeResource(getResources(), R.drawable.login_background)));

    @Override
    public void onDestroy() {
    super.onDestroy();
    recycleView(findViewById(R.id.login_layout));
    }

    private void recycleView(View view) {
    if (view != null) {
    Drawable bg = view.getBackground();
    if (bg != null) {
    bg.setCallback(null);
    ((BitmapDrawable) bg).getBitmap().recycle();
    view.setBackgroundDrawable(null);
    }
    }
    }

    에러 코드입니다.
    08-06 13:30:46.883 23782-23782/com.honma.mytestapp E/AndroidRuntime﹕ FATAL EXCEPTION: main
    Process: com.honma.mytestapp, PID: 23782
    java.lang.RuntimeException: Unable to destroy activity {com.honma.mytestapp/com.honma.mytestapp.IntroActivity}: java.lang.NullPointerException
    at android.app.ActivityThread.performDestroyActivity(ActivityThread.java:3851)
    at android.app.ActivityThread.handleDestroyActivity(ActivityThread.java:3869)
    at android.app.ActivityThread.access$1500(ActivityThread.java:170)
    at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1357)
    at android.os.Handler.dispatchMessage(Handler.java:102)
    at android.os.Looper.loop(Looper.java:146)
    at android.app.ActivityThread.main(ActivityThread.java:5635)
    at java.lang.reflect.Method.invokeNative(Native Method)
    at java.lang.reflect.Method.invoke(Method.java:515)
    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1291)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1107)
    at dalvik.system.NativeStart.main(Native Method)
    Caused by: java.lang.NullPointerException
    at com.honma.mytestapp.IntroActivity.recycleView(IntroActivity.java:62)
    at com.honma.mytestapp.IntroActivity.onDestroy(IntroActivity.java:54)
    at android.app.Activity.performDestroy(Activity.java:5752)
    at android.app.Instrumentation.callActivityOnDestroy(Instrumentation.java:1123)
    at android.app.ActivityThread.performDestroyActivity(ActivityThread.java:3833)
                at android.app.ActivityThread.handleDestroyActivity(ActivityThread.java:3869)
                at android.app.ActivityThread.access$1500(ActivityThread.java:170)
                at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1357)
                at android.os.Handler.dispatchMessage(Handler.java:102)
                at android.os.Looper.loop(Looper.java:146)
                at android.app.ActivityThread.main(ActivityThread.java:5635)
                at java.lang.reflect.Method.invokeNative(Native Method)
                at java.lang.reflect.Method.invoke(Method.java:515)
                at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1291)
                at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1107)
                at dalvik.system.NativeStart.main(Native Method)

    • 대세를 따르지 않고 대세를 만드는 매몰 2015.08.22 12:25 신고 수정/삭제

      에러코드를 보면 ((BitmapDrawalbe)bg).getBitmap().recycle(); 에서
      널 포인트가 잡힌 듯 하네요

      이는 getBitmap() 에서 bitmap 객체가 널이기 때문인것으로 보여요
      전체 소스가 어떤지 알수 없어 정확히는 모르겠지만
      이부분만 본다면 bitmap을 recycle()하기 전 또는 후에 다른곳에서
      널을 줬을 가능성이 있을것 같아요

      이를 미연에 방지하기 위해서
      Bitmap bitmap = ((BitmapDrawalbe)bg).getBitmap();
      if(bitmap != null)
      bitmap.recycle();
      요런식으로 고쳐 써 보심이 어떨까 싶네요

      알구... 역시 저는 아직 멀었나 봅니다ㅎㅎ
      찾지못한 버그에 항상 마음이 아프네요ㅠㅠ

  • 혼마 2015.08.06 16:00 신고 ADDR 수정/삭제 답글

    아 그리고 한가지 더 있습니다.
    안드로이드 5.0.0 버전에서 1.2M 크기의 이미지를 배경화면으로 설정하기 위해 위의 소스를 이용했으나, 적용되지 않았습니다.
    또한 원래 4.4.4 버전에서 처음에는 됐으나 나중에는 로드되지 않는 현상이 발생했습니다.
    혹시 이미지 크기가 너무 커서 그런건가요? 아니라면 다른 이유가 있을지 궁금합니다.

    • 대세를 따르지 않고 대세를 만드는 매몰 2015.08.22 14:32 신고 수정/삭제

      제가 보기에는 버전도 무시할 순 없지만
      기기별 문제가 더 크지 않나 싶어요

      여러종류의 단말기로 개발을 하다 보면 버전이 같아도
      큰이미지가 괞찮은 폰이 있고 바로 죽는 폰도 있었죠

      그래서 저도 용량이 큰 통이미지를 그대로 잘 쓰진 않아요
      잘게 부셔서 여러 뷰에 담죠ㅎㅎ

      어잿든 혼마님께서 지직해 주신 내용을 바탕으로
      살펴보고 더 보안도록 해보겠습니다

      하지만 어디까지나 프로그래머의 역량은
      퀄러터를 최대한 유지한체 리소스를 최소화 하는 것이에요
      작은 이미지를 써서 최대한 퀄러티있게 보이게 하는 방법이
      더 좋은 방법이죠~

  • 찾는이 2015.12.31 15:46 신고 ADDR 수정/삭제 답글

    안녕하세요! 메모리 관련 오류찾다가 들리게되었습니다. 좋은글이라 후에 참조할까 싶어 링크를 가져가도 될까 해서 덧글 남겼습니다!

    조금은 제게 난이도가 있어보여서 일단은 이미지 사이즈를 줄이는것으로 해결을 봐두었습니다. 아무쪼록 필요하면 또 들리겠습니다^^

개발/android 2013.08.23 11:57

페이스북앱이 설치되어 있을때 페이스북 api로 로그인이 안되는 문제

android facebook api(sdk)로 로그인 작업을 하다 보면 발생하는 치명적인 문제가 있다


폰안에 페이스북앱이 설치되어 있지 않을때는 로그인 화면으로 웹뷰가 띄어지면서 잘 로그인된다.


하지만 페이스북앱이 설치되어 있을때에는 웹뷰가 아닌 페이스북앱이 실행되면서 로그인이 이루어지는데 여기서 버그인지 아님 원래 그런건지 로그인이 계속 실패한다.  

 

처음에는 내가 잘못 코딩한지 알았는데 예제를 돌려봐도 똑같은 현상이 발생했다.


그래서 몇일동안 삽질끝에 해결책을 찾아내었다^^


해결책은 역시 예제안에 숨어있었다ㅎㅎ 예제중에 SwitchUserSample이라는 프로젝트가 있는데

이 예제에서 로그인 유저를 바꿀때에는 항상 로그인 화면 웹뷰가 뜨면서 잘되는것이다ㅎㅎ

이부분을 집중 분석하여 아래와 같은 해결코드를 작성하였다~


public Session mSession;


mSession = new Session.Builder(this).setTokenCachingStrategy(new SharedPreferencesTokenCachingStrategy(this)).build();

Session.OpenRequest openRequest = new Session.OpenRequest(this);

openRequest.setLoginBehavior(SessionLoginBehavior.SUPPRESS_SSO);

openRequest.setRequestCode(Session.DEFAULT_AUTHORIZE_ACTIVITY_CODE);

mSession.openForRead(openRequest); 


또한 로그인후 권한설정에서도 아래와 같이 하면 된다...


mPermissions = Arrays.asList("publish_actions", "user_videos", "read_stream", "share_item");


Session.NewPermissionsRequest newPermissionsRequest = new Session.NewPermissionsRequest(this, mPermissions)            .setLoginBehavior(SessionLoginBehavior.SUPPRESS_SSO).setDefaultAudience(SessionDefaultAudience.EVERYONE).setRequestCode(Session.DEFAULT_AUTHORIZE_ACTIVITY_CODE);

session.requestNewPublishPermissions(newPermissionsRequest);


안드로이드 페이스북 연동을 하시는 분들께서는 저처럼 삽질하지 마시고 위내용을 참고하여 바로바로 진행하세요~


위내용으로 인해 문제를 해결하셨거나 유용한 정보라고 생각되신다면 댓글 한번 달아주셔요~ ㅎㅎ

그리고 퍼가실때는 반드시 출처 남겨주세요~ ㅎㅎ 







 

 매몰: 고영진 대표/개발자


 고영진모바일 1인기업의 하나뿐인 사람. 

 그밖에 동반자 노트북,스마트폰 

     실패만 하고 있어도 꿈을 포기하지 않는 남자. 

     제가 직접 경험하고 습득한 지식을 위주로 올릴게요. 







사업자 정보 표시
고영진모바일 | 고영진 | 서울특별시 관악구 낙성대동 서울대연구공원 SK상생혁신센터 | 사업자 등록번호 : 109-11-82076 | TEL : 010-9990-3674 | Mail : gyjmeba@hanmail.net | 통신판매신고번호 : 2010-서울강서-0217호 | 사이버몰의 이용약관 바로가기
  • 감사합니다~ 2013.08.27 10:57 신고 ADDR 수정/삭제 답글

    안그래도 찾아보고 있었는데 좋은 정보 감사드립니다^^

  • 임민영 2013.10.22 09:22 신고 ADDR 수정/삭제 답글

    이것 때문에 반나절을 삽질했는데.. 감사합니다..ㅜㅜ

  • 감사합니다 2013.12.21 01:46 신고 ADDR 수정/삭제 답글

    퍼갈께여 좋은정보 감사합니다. ㅎㅎ

  • 진:D 2014.01.20 20:20 신고 ADDR 수정/삭제 답글

    에뮬레이터에서는 로그인이 잘되고 페북앱만 거치면 로그인이 안되서 스트레스받고 있었는데 위 포스팅을 보고 한번에 해결할 것 같습니다 : ) 정말 감사합니다!

  • 여우좌 2014.02.12 13:10 신고 ADDR 수정/삭제 답글

    필요한 정보 였는데 잘보고 갑니다.
    괜스레 해쉬 코드만 바꿀뻔 했네요
    괜찮으시다면 글좀 퍼가도 될까요?

  • 감사합니다~ 2014.03.25 14:01 신고 ADDR 수정/삭제 답글

    하루종일 개고생하다가 이글보고 해결했네여 감사합니다 ㅎㅎ

  • 질문좀 남기겠습니다. 2014.04.02 12:28 신고 ADDR 수정/삭제 답글

    이 글을 보고 문제 한개를 해결했는데 다른 문제가 한가지 생겼습니다. 이전 소스에서 보면 네트워크가 끊긴 상태에서 로그인을 시도를 해도 로그인 창이 안나왔습니다. 로그인 창 없이 바로 로그인 시도를 하여는데, 위 소스의 경우 네트워크가 끊기면 로그인 창이 나오게 되네요. 다시 네트워크를 연결 해도 로그인창이 나오고요. 한마디로 세션이 사라졌다는 소리인데. 이 문제에 대한 처리 방법은 없을까요?

    • 대세를 따르지 않고 대세를 만드는 매몰 2014.04.05 15:40 신고 수정/삭제

      답글이 늦었네요… 로그인창 없이 바로 로그인이 되어야 하는게 정상인데 그것에 대해서는 저도 미처 살펴보지 못했네요… 시간내서 해결책을 찾은 다음 다시 글을 올려볼게요~

  • 정우정우열매 2014.11.17 15:41 신고 ADDR 수정/삭제 답글

    로그인 팝업이 안떠서 삽질하다가 이거 보고 해결하였습니다. 감사합니다.
    처음엔 킷캣에서 안되는 줄 알았는데 기존 페이스북 앱 때문에 그랫던 거네요.

  • 복덩어리 2014.12.29 16:47 신고 ADDR 수정/삭제 답글

    이것때문에 두달간 헤맸습니다. ㅠㅠ

    SSO_WITH_FALLBACK 옵션으로 되어있었네요 ㅠㅠ

    완전 삽질 했다는.. 정말 감사합니다! 복받으실 거예요!

  • 이방진 2015.03.24 16:01 신고 ADDR 수정/삭제 답글

    저도 삽질했는데.. 이게 알고보니깐 apk로 말지 않고 디버그로 실행하면 그렇게 되더라구요 꼭 저렇게 옵션값줘서 할필요 없습니다 ㅠㅠ..

TOTAL 69,581 TODAY 10