블로그 이미지
매몰

모바일 어플리케이션 개발 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.09.27 13:30

sqlite의 time이 자정을 인식 못할 경우, 간단한 꼼수 해결법



안드로이드의 sqlite 데이터베이스를 사용하는 도중 이상한 점을 하나 발견했다.


time 데이터타입에서 00시 또는 24시, 즉 자정일때 time() 함수가 먹히질 않는다는 점이다.


예를 들면,

(time 데이터타입의 field1 시각 +  int 데이터타입의 field2 초) > 특정시각


을 비교할때


SELECT * FROM table1 WHERE time(field1, field2 ||' seconds') > 23:59:30

와 같이 사용한다.


하지만 이때, field1이 23:59:00 이고 field2가 60초 이상이면 자정이 넘어가면서 SELECT 결과가 항상 아무것도 안나오게 된다. field1이 24:00:00 이상이어도 마찬가지다.


이걸 해결하기 위해 만방으로 살펴봤지만... 역시 코딩은 꼼수가 최고다.


그냥... 자정이 넘어갈것 같으면 시간을 앞당겨주면 된다ㅎㅎ



다음과 같이 해보자~

private String getTime(String time, String addtime) {
return "time('" + time + "'" + addtime + ")";
}

private String getTime(String field, String addfield, String addtime) {
return "time(" + field + "," + addfield + "||' seconds'" + addtime + ")";
}

private Cursor fetch(SQLiteDatabase db, String time) {
//특정시각 time이 21시보다 크면 3시간을 앞당겨 준다.
String addText = Integer.parseInt(time.substring(0, 2)) < 22 ? "" : ",'-3 hours'";

return db.rawQuery("SELECT * FROM table1 WHERE " + getTime("field1", "field2", addText) + "<" + getTime(time, addText), null);
}

자정이 넘어 갔을때 시간을 앞당겨 비교해 주면 정상적으로 작동한다. 


수제앱장인


 

 

CEO

Developer

S/W Enginner

고영진


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



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

간단한 코딩으로 큰따음표("")로 묶인 CSV 파일 보기



CSV란 콤마(,)로 구분한 일련의 데이터들을 말한다.

예를 들면 아래와 같다.



고양이,강아지,곰,상어,토끼 


100,20,55,177,83



이런 형식은 서로 호환 되지 않는 프로그램끼리 데이터들을 주고 받을때 유용하게 쓰인다.

또한 매우 간단하기 때문에 부담도 없다.


하지만 흔한 문제점 하나가 있다.

콤마가 있는 데이터가 있을 경우다.



56

10,000

1,500

300

890

2,360,050

74



금액을 표시할때 위와 같이 종종 단위 콤마를 쓰는데

이것을 CSV로 표현하면,



56,10,000,1,500,300,890,2,360,050,74



가 된다. 의도와는 다르게 데이터가 



56

10

100

1

500

300

890

2

360

050

74



로 나누어져 버렸다

그래서 콤마가 있는 데이터를 큰따음표("")로 묶어 버린다



56,"10,000","1,500",300,890,"2,360,050",704



큰따음표로 모두 묶어버리면 좋겠지만 

콤마가 없는 데이터는 그대로 두고 (위에서 56, 300, 890, 704)

콤마가 있는 데이터만 묶는 경우가 있다


우리는 이럴때를 가정해서 살펴볼 것이다.

왜냐하면 보통 이런 경우를 고려하지 않는 뷰어가 꽤 있기 때문이다

당황 하지 말고 코딩인으로써 해결하자~




먼저, 첫번째 큰따음표를 기준으로 나누자. 


private String[] divideText(String text) {
//문자열안에서 첫번째 큰따음표의 위치를 찾는다
int index = text.indexOf("\"");
if (index >= 0) {
//큰따음표가 첫문자 또는 마지막에 있으면 널을 반환하여 알린다
int resultindex = index + 1;
return new String[]{(index > 0 ? text.substring(0, index) : null), (resultindex < text.length() ? text.substring(resultindex) : null)};
}

//찾지 못하면 널을 반환하여 알린다
return null;
}



그 다음은 위의 함수로

큰따음표 안의 문자열과 바깥(앞,뒤) 문자열을 추출하자 


바깥 앞  ""  바깥 뒤

56,"10,000","1,500",300,890,"2,360,050",704


private String[] getText(String text) {
//데이터 문자열을 첫번째 큰따음표로 나눈다
String[] subtexts = divideText(text);
if (subtexts == null) {
//큰따음표가 더이상 없다면
//바깥 앞 문자열 마지막에 콤마를 찍는다
//이는 언제나 문자열 양끝에 콤마가 존재해야 하기 때문이다
if (text.length() > 0)
return new String[] {text + ",", null, null};
}
else {
String[] result = {null, null, null};

//divideText의 앞 문자열을 바깥 앞 문자열로 설정한다
result[0] = subtexts[0];

//divideText의 뒤 문자열은 다시 큰따음표로 나눈다
String[] backtexts = divideText(subtexts[1]);
if (backtexts != null) {
//다시 나누어진 앞 문자열은 큰따음표 안의 문자열
result[1] = backtexts[0];

//뒤 문자열은 큰따음표 바깥 뒤 문자열
result[2] = backtexts[1];
}

return result;
}

return null;
}



자, 이젠 데이터를 추출해 보자


private ArrayList<String> getItemList(String text) {
ArrayList<String> itemlist = new ArrayList<>();

while (text != null) {
String[] subtexts = getText(text);
if (subtexts == null)
break;

//바깥 앞의 문자열은 큰따음표가 없는 데이터이다
String fronttext = subtexts[0];
if (fronttext != null && fronttext.length() > 1) {
//양끝의 콤마를 제거 한다
if (fronttext.charAt(0) == ',')
fronttext = fronttext.substring(1);

int lastindex = fronttext.length() - 1;
if (fronttext.charAt(lastindex) == ',')
fronttext = fronttext.substring(0, lastindex);

//콤마를 기준으로 나눠서 저장한다
String[] addtexts = fronttext.split(",");
for (int i = 0 ; i < addtexts.length ; i++)
itemlist.add(addtexts[i]);

//만약 마지막에 콤마가 있다면
//빈데이터가 하나 더 있는것이므로 따로 저장한다
if (fronttext.charAt(fronttext.length() - 1) == ',')
itemlist.add("");
}

//안의 문자열은 그대로 저장한다
String contentstext = subtexts[1];
if (contentstext != null)
itemlist.add(contentstext);

//바깥 뒤의 문자열은 계속 데이터를 추출할 수 있도록 해준다
text = subtexts[2];
}

return itemlist;
}



확인해 보자~


ArrayList<String> itemlist = getItemList("56,\"10,000\",\"1,500\",300,890,\"2,360,050\",704");

for (int i = 0 ; i < itemlist.size() ; i++)
Log.e("MyLog", "itemlist: " + itemlist.get(i));


itemlist: 56

itemlist: 10,000

itemlist: 1,500

itemlist: 300

itemlist: 890

itemlist: 2,360,050

itemlist: 704




 

 



수제 앱 장인: 고영진


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

  

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

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

 




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

막대 그래프의 처음은 이렇게





가장 기본이 되는 그래프인 막대 그래프를 만들어 보자~


위와 같이 막대와 그 수치를 넣을 것이다

예전에 올렸던 꺽은선 그래프를 살짝 변경해서 코딩했다.



private Paint mLinePaint, mTextPaint;

private float mTextGap;
private int[] mPoints, mPointX, mPointY;
private int mUnit, mOrigin, mDivide;

public GraphView(Context context, AttributeSet attrs) {
super(context, attrs);

setTypes(context, attrs);
}

//그래프 옵션을 받는다
private void setTypes(Context context, AttributeSet attrs) {
TypedArray types = context.obtainStyledAttributes(attrs, R.styleable.GraphView);

//수치 옵션
Paint paint = new Paint();
paint.setColor(types.getColor(R.styleable.GraphView_textColor, Color.BLACK));
paint.setTextSize(types.getDimension(R.styleable.GraphView_textSize, 0));
paint.setTextAlign(Paint.Align.CENTER);
mTextPaint = paint;

//막대와 수치와의 거리
mTextGap = types.getDimension(R.styleable.GraphView_textGap, 0);

//막대 옵션
paint = new Paint();
paint.setColor(types.getColor(R.styleable.GraphView_lineColor, Color.BLACK));
paint.setStrokeWidth(types.getDimension(R.styleable.GraphView_lineThickness, 0));
mLinePaint = paint;
}

//그래프 정보를 받는다
public void setPoints(int[] points, int unit, int origin, int divide) {
mPoints = points; //y축 값 배열

mUnit = unit; //y축 단위
mOrigin = origin; //y축 원점
mDivide = divide; //y축 값 갯수
}

//그래프를 만든다
public void draw() {
int height = getHeight();
int[] points = mPoints;

//x축 막대 사이의 거리
float gapx = (float)getWidth() / points.length;

//y축 단위 사이의 거리
float gapy = height / mDivide;

float halfgab = gapx / 2;

int length = points.length;
mPointX = new int[length];
mPointY = new int[length];

for(int i = 0 ; i < length ; i++) {
//막대 좌표를 구한다
int x = (int)(halfgab + (i * gapx));
int y = (int)(height - (((points[i] / mUnit) - mOrigin) * gapy));

mPointX[i] = x;
mPointY[i] = y;
}
}

//그래프를 그린다(onCreate 등에서 호출시)
public void drawForBeforeDrawView() {
//뷰의 크기를 계산하여 그래프를 그리기 때문에 뷰가 실제로 만들어진 시점에서 함수를 호출해 준다
getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
draw();

if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN)
getViewTreeObserver().removeGlobalOnLayoutListener(this);
else
getViewTreeObserver().removeOnGlobalLayoutListener(this);
}
});
}

@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);

if(mPointX != null && mPointY != null) {
int length = mPointX.length;

int bottom = getHeight();
for (int i = 0; i < length; i++) {
int x = mPointX[i];
int y = mPointY[i];

//믹대 위 수치를 쓴다
canvas.drawText("" + mPoints[i], x, y - mTextGap, mTextPaint);

//믹대를 그린다
canvas.drawLine(x, y, x, bottom, mLinePaint);
}
}
}



옵션 attrs.xml 이다


<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="GraphView">
<attr name="textColor" format="color"/>
<attr name="textSize" format="dimension"/>
<attr name="textGap" format="dimension"/>

<attr name="lineColor" format="color"/>
<attr name="lineThickness" format="dimension"/>
</declare-styleable>
</resources>



layout은 이렇다


<?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">

<kr.gyjmoble.graphtest.GraphView android:id="@+id/GraphView"
xmlns:gyjmobile="http://schemas.android.com/apk/res/kr.gyjmoble.graphtest"
android:layout_width="match_parent" android:layout_height="match_parent"
gyjmobile:textColor="#ec4800" gyjmobile:textSize="15sp"
gyjmobile:textGap="2dp"
gyjmobile:lineColor="#0073cc" gyjmobile:lineThickness="20dp"/>

</LinearLayout>



자 이젠 사용하자


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

//그래프에 들어갈 수치 배열
int[] points = {5, 3, 7, 8, 4, 3, 3, 6, 4, 1};

GraphView graphview = (GraphView) findViewById(R.id.GraphView);

//단위는 1씩, 원점은 0, 총 10줄로 나누어진 그래프를 그린다
graphview.setPoints(points, 1, 0, 10);
graphview.drawForBeforeDrawView();
}



처음은 이렇게 간단하게 출발해서 다양한 그래프를 만들면 된다~




 

 



수제 앱 장인: 고영진


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

  

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

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

 




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

재귀 함수의 완벽한 이해

재귀 함수란 자기 자신을 다시 호출하는 함수를 말한다.

하지만 이러한 개념을 알고 있어도 직접 구현할려고 하면 헷갈린다.


특히, 분기를 위한 반복문이 함수내에 들어간다면 더더욱 그렇다.


거리 계산 다이어그램을 예로 해서 살펴보자





위 다이어그램의 숫자들은 각 점끼리의 거리이다.

즉, 총 거리는 숫자들을 모두 더한 13이고,

D까지는 3 + 2 + 4 = 9가 된다.


재귀 함수로 코드화 해보자 


private static int NAME_A = 0;
private static int NAME_B = 1;
private static int NAME_C = 2;
private static int NAME_D = 3;
private static int NAME_E = 4;
private static int NAME_F = 5;
private static int NAME_G = 6;
private static int NAME_H = 7;


private int getDistance(int[] points, int index, int searchindex, int distance) {
//거리 누적
distance += points[index];
Log.d("MyLog", "getDistance: " + distance);

//목적지까지 도착하면 멈춤
if(index == searchindex)
return distance;

//다음 점으로 재귀 호출
return getDistance(points, ++index, searchindex, distance);
}


//점의 거리 배열
int[] points = {0, 3, 2, 4, 3, 1};

//D까지의 거리 계산
int distance = getDistance(points, NAME_A, NAME_D, 0);

Log.d("MyLog", "Distance: " + distance);


결과 :


D/MyLog: getDistance: 0

D/MyLog: getDistance: 3

D/MyLog: getDistance: 5

D/MyLog: getDistance: 9

D/MyLog: Distance: 9




여기까지는 원래 반복문 하나로도 구현 가능하다

하지만 중간에 분기가 포함되면 재귀 함수의 면모가 드러난다



위 다이어그램은 C에서 두갈래로 갈라진다

코드로는 재귀 함수에 반복문을 추가해준다


//점 거리 클래스
private class Point {
public Point[] mNextPoint;
public int mDistance;

public Point(int distance, Point[] nextpoint) {
mDistance = distance;
mNextPoint = nextpoint;
}
}


private int getDistance(Point point, Point searchpoint, int distance) {
while (true) {
//거리 누적
distance += point.mDistance;
Log.d("MyLog", "getDistance: " + distance);

//목적지까지 도착하면 멈춤
if (point == searchpoint)
return distance;

//다음 점 찾기
Point[] nextpoints = point.mNextPoint;
if(nextpoints == null) {
//더이상 길이 없으면 종료
break;
}
else {
for (int i = 0; i < nextpoints.length; i++) {
if(i == 0) {
//첫번째 길은 계속 반복문을 돌림
point = nextpoints[i];
}
else {
//두번째 길부터는 재귀 호출
int result = getDistance(nextpoints[i], searchpoint, distance);

//목적지를 찾았을때에만 누적 거리 반환
//그렇지 않으면 이어서 반복문을 돌림
if(result >= 0)
return result;
}
}
}
}

return -1;
}


//점의 거리 배열
Point[] points = new Point[8];
points[NAME_H] = new Point(3, null);
points[NAME_G] = new Point(2, new Point[] {points[NAME_H]});
points[NAME_F] = new Point(1, null);
points[NAME_E] = new Point(3, new Point[] {points[NAME_F]});
points[NAME_D] = new Point(4, new Point[] {points[NAME_E]});
points[NAME_C] = new Point(2, new Point[] {points[NAME_D],points[NAME_G]});
points[NAME_B] = new Point(3, new Point[] {points[NAME_C]});
points[NAME_A] = new Point(0, new Point[] {points[NAME_B]});

//A에서 G까지의 거리 계산
int distance = getDistance(points[NAME_A], points[NAME_G], 0);
Log.d("MyLog", "Distance G: " + distance);

//A에서 E까지의 거리 계산
distance = getDistance(points[NAME_A], points[NAME_E], 0);
Log.d("MyLog", "Distance E: " + distance);


결과 : 


D/MyLog: getDistance: 0

D/MyLog: getDistance: 3

D/MyLog: getDistance: 5

D/MyLog: getDistance: 7

D/MyLog: Distance G: 7


D/MyLog: getDistance: 0

D/MyLog: getDistance: 3

D/MyLog: getDistance: 5

D/MyLog: getDistance: 7

D/MyLog: getDistance: 10

D/MyLog: getDistance: 9

D/MyLog: getDistance: 12

D/MyLog: Distance E: 12




재귀 함수의 핵심은 반환값이다.


본류와 지류를 구분해 놓고

본류는 반복문이 계속 되게 놔두고 지류만 재귀 호출을 한다.

그리고 성공인지 실패인지 반환값을 받아 계속할지 끝낼지 

결정하면 된다. 



 

 



수제 앱 장인: 고영진


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

  

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

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

 




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

TimePicker, DatePicker, NumberPicker의 폰트 바꾸기



시간을 쉽게 선택할 수 있는 TimePicker,

날짜를 쉽게 선택할 수 있는 DatePicker


이것들은 NumberPicker로 이루어진 UI이다.

그래서 이 Picker들의 폰트를 바꾸기 위해서는  NumberPicker의 구조를 알아야 한다.


NumberPicker는 현재 선택된 숫자를 보여주는 TextView와 

그 주위의 숫자를 스크롤하며 보여주는 휠 뷰로 구성된다.

(스타일을 Theme.Holo.Light 로 적용했을 경우)


즉, 폰트를 바꿔줄때 이 두가지를 모두 바꿔줘야 한다는 의미다.


public void setTypeface(NumberPicker picker, Typeface typeface) {
//주위의 숫자들을 보여주는 휠 뷰
try {
//클래스의 휠 Paint 객체를 꺼내 폰트를 적용
Field field = NumberPicker.class.getDeclaredField("mSelectorWheelPaint");
field.setAccessible(true);
((Paint)field.get(picker)).setTypeface(typeface);

} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}

//선택된 숫자를 보여주는 TextView
int count = picker.getChildCount();
for(int i = 0 ; i < count ; i++) {
//자식뷰로 꺼내 폰트를 적용
View view = picker.getChildAt(i);
if (view instanceof TextView)
((TextView)view).setTypeface(typeface);
}
}



이를 TimePicker에 적용하면 이렇다.

DatePicker도 같은 방식으로 하면 된다. 


public class MyTimePicker extends TimePicker {

public MyTimePicker(Context context) {
super(context);
}

public MyTimePicker(Context context, AttributeSet attrs) {
super(context, attrs);
}

public void setTypeface(Typeface typeface) {
LinearLayout layout = (LinearLayout)getChildAt(0);

for(int i = 0 ; i < layout.getChildCount() ; i++) {
LinearLayout layout1 = (LinearLayout) layout.getChildAt(i);

//NumberPicker가 있는 자식뷰에서 적용
for(int j = 0 ; j < layout1.getChildCount() ; j++) {
View view = layout1.getChildAt(j);
if (view instanceof NumberPicker)
setTypeface((NumberPicker)view, typeface);
}
}
}
}


 

 



수제 앱 장인: 고영진


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

  

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

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

 




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

사각형 말고 원형 충돌검사

게임상에서 가장 기본적인 충돌검사는 사각, 원, 선 검사라고 생각한다

왜냐하면 이 세가지의 개념을 조합하면 좀더 정밀한 다각형, 픽셀 검사를 할수 있기 때문이다


여기서는 단순하게 원형끼리의 검사를 살펴보겠다


원은 한점에서 일정거리 만큼의 영역을 가지는 도형이다

그래서 두 원의 영역이 겹치는 조건은 중심점끼리의 거리로 판단한다.




위와 같이 중심점 사이의 거리가 두 반지름의 합보다 크면 서로 떨어져 있는 것이다.

두점 사이의 거리는 중3때 배우는 공식을 사용하면 된다 (http://mathbang.net/138)





반대의 경우로 중심점 거리가 반지름의 합보다 작아지면 겹치게 된다.

즉, 이때가 충돌한 것이다.





충돌이긴 한데.. 

완전히 겹치는, 다시 말해 한 원에 완전히 포함되는 경우는 반지름의 차로 계산한다.

거리가 반지름의 차보다 작으면 그런 경우이다.

이때, 당연히 반지름이 더 큰 원이 작은 원을 포함하게 되는것이다.



지금까지 살펴본 개념을 소스로 표현해 보자.


public static final int RESULT_FALSE = 0;
public static final int RESULT_TURE = 1;
public static final int RESULT_PERFECT_1 = 2;
public static final int RESULT_PERFECT_2 = 3;

public int checkCircles(float x1, float y1, float r1, float x2, float y2, float r2) {
//두 원의 중심점 사이의 거리를 구함
float dx = x2 - x1;
float dy = y2 - y1;
float distance = (float)Math.sqrt((dx * dx) + (dy * dy));

//중심점 사이의 거리와 두 원의 반지름 길이 비교
if(distance <= r1 - r2) //반지름 차보다 작고 원1이 더 클때
return RESULT_PERFECT_1; //원1 안에 원2가 있음
else if(distance <= r2 - r1) //반지름 차보다 작고 원2가 더 클때
return RESULT_PERFECT_2; //원2 안에 원1이 있음
else if(distance <= r1 + r2) //반지름 합보다 작을때
return RESULT_TURE; //일부가 겹침

return RESULT_FALSE; //반지름 합보다 클때 겹치지 않음
}


 

 



수제 앱 장인: 고영진


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

  

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

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

 




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

알기 쉬운 안드로이드의 방위각

안드로이드 각도


게임에서 케릭터를 이동시킬때 방위각이 필요하다.

이때 각도를 라디안으로 계산하면 편리하다.




시작점에서 끝점 방향으로 이동할때의 방위각을 구해보자.

public float getRadian(float startx, float starty, float endx, float endy) {
float x = endx - startx;
float y = endy - starty;

return (float)Math.atan2(y, x);
}



거꾸로 방위각에서 방향벡터를 구해보자.


public float[] getNormal(float radian) {

return new float[] {(float)Math.cos(radian), (float)Math.sin(radian)};
}



케릭터 이미지를 방향별로 회전해야 할때가 있을것이다.

이때에는 이미지 회전을 위해 Matrix 객체에 각도를 넣어야 한다.

라디안을 각도로 변환해보자.


public float getDegress(float radian) {

return (float)Math.toDegress(radian);
}



 

 



수제 앱 장인: 고영진


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

  

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

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

 




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

Bitmap 이미지를 byte로 바로 보낸다고? 그것도 소켓으로?



군대를 막 전역했을때 다시 완벽한 민간인이 되기 위해 책한권을 샀었다

바로 "TCP/IP 소켓 프로그래밍" 이었다


이때 처음 접한 네크워크 코딩술로

간단한 원도우용 채팅프로그램을 만들었고 

졸업후에는 PC와 스마트폰간의 연결앱인 "키보드에디터"를 출시하였다. 물론 쫄닥 망했다ㅎㅎ


어쨌든 그후에도 가끔씩 소켓기반의 앱을 만들면서 자연스럽게 이미지 전송도 하게되었다

여기서는 비트맵이미지를 파일형태가 아닌 Byte단위의 원본으로 바로 보내는 것을 다뤄보겠다.


파일도 결국은 Byte로 보내는것이지만... 

외부파일이 아닌 내부이미지를 불러와 바로 전송할때 유용할것이다~





-서버

public class Server implements Runnable {

@Override
public void run() {
try {
ServerSocket socket = new ServerSocket(9999);

while(true) {
Socket clientsoket = socket.accept();

//클라이언트로 보낼 출력스트림을 얻는다
DataOutputStream os = new DataOutputStream(clientsoket.getOutputStream());

//이미지를 불려온다
byte[] data = getImageByte(BitmapFactory.decodeResource(getResources(), R.drawable.image));

//비트맵이미지의 총크기를 4byte배열에 담아서 먼저 보낸다 (이때 꼭 4byte일 필요는 없다. 마음내키는대로~)
//총크기를 보내는 이유는 비트맵이미지가 전부 전송된 시점을 클라이언트에 알리기 위함이다
byte[] size = getByte(data.length);
os.write(size, 0, size.length);
os.flush();

//실제 데이터를 보낸다
os.write(data, 0, data.length);
os.flush();
}

socket.close();

} catch (IOException e) {
e.printStackTrace();

}
}
}

//비트맵의 byte배열을 얻는다
public byte[] getImageByte(Bitmap bitmap) {
ByteArrayOutputStream out = new ByteArrayOutputStream();
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, out);

return out.toByteArray();
}
//숫자를 byte형태로 바꾼다
private byte[] getByte(int num) {
byte[] buf = new byte[4];
buf[0] = (byte)( (num >>> 24) & 0xFF );
buf[1] = (byte)( (num >>> 16) & 0xFF );
buf[2] = (byte)( (num >>> 8) & 0xFF );
buf[3] = (byte)( (num >>> 0) & 0xFF );

return buf;
}





-클라이언트

public class Client implements Runnable {

@Override
public void run() {
try {
Socket socket = new Socket("IP주소", 9999);

BufferedInputStream bis = new BufferedInputStream(socket.getInputStream());

while(mRun) {
byte[] imagebuffer = null;
int size = 0;

byte[] buffer = new byte[10240];

int read;
while((read = bis.read(buffer)) != -1 && mRun) {
if (imagebuffer == null) {
//처음 4byte에서 비트맵이미지의 총크기를 추출해 따로 저장한다
byte[] sizebuffer = new byte[4];
System.arraycopy(buffer, 0, sizebuffer, 0, sizebuffer.length);
size = getInt(sizebuffer);
read -= sizebuffer.length;

//나머지는 이미지버퍼 배열에 저장한다
imagebuffer = new byte[read];
System.arraycopy(buffer, sizebuffer.length, imagebuffer, 0, read);
}
else {
//이미지버퍼 배열에 계속 이어서 저장한다
byte[] preimagebuffer = imagebuffer.clone();
imagebuffer = new byte[read + preimagebuffer.length];
System.arraycopy(preimagebuffer, 0, imagebuffer, 0, preimagebuffer.length);

System.arraycopy(buffer, 0, imagebuffer, imagebuffer.length - read, read);
}

//이미지버퍼 배열에 총크기만큼 다 받아졌다면 이미지를 저장하고 끝낸다
if(imagebuffer.length >= size) {
Bundle bundle = new Bundle();
bundle.putByteArray("Data", imagebuffer);

Message msg = mResultHandler.obtainMessage();
msg.setData(bundle);
mResultHandler.sendMessage(msg);

imagebuffer = null;
size = 0;
}

}
}

socket.close();
bis.close();

} catch (IOException e) {
e.printStackTrace();
}
}
}

//byte배열을 숫자로 바꾼다
private int getInt(byte[] data) {
int s1 = data[0] & 0xFF;
int s2 = data[1] & 0xFF;
int s3 = data[2] & 0xFF;
int s4 = data[3] & 0xFF;

return ((s1 << 24) + (s2 << 16) + (s3 << 8) + (s4 << 0));
}
//이미지뷰에 비트맵을 넣는다
public Handler mResultHandler = new Handler() {
public void handleMessage(Message msg) {
byte[] data = msg.getData().getByteArray("Data");

((ImageView) findViewById(R.id.ImageView)).setImageBitmap(BitmapFactory.decodeByteArray(data, 0, data.length));
}
};



참고로~

언급했듯이 전송형태에 있어서

위 방법말고도 불러온 이미지를 파일로 만들어 보내는 형태도 있고,


이미지버퍼 배열을 데이터에 담는 방식에 있어서도

미리 받은 총크기로 배열을 생성한 뒤 계속 이어서 받는 방식도 있다


편한데로 응용해서 쓰시길 바란다



 

 



수제 앱 장인: 고영진


(주)고영진모바일

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

  

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

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

 





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

    클라이언트의 mRun이 선언이 안되어있는데,
    어떤 변수인가요?ㅠㅠ

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

      mRun 을 깜박했네요ㅎㅎ
      mRun은 만약에 있을 무한루프를 방지하기 위해서 넣은거에요
      그냥 빼셔도 되요

      만약 넣는다면
      Activity에 mRun을 전역변수로 추가하시고
      onCreate() 에 mRun = true
      onDestroy() 에 mRun = false
      를 해주시면 됩니다~

개발/android 2015.09.21 15:01

DialogFragment에 DismissListener를 쉽게 사용하는 방법




DialogFragment를 종료한 후 결과를 받아서 처리해야 할때가 있다.


당연히 이때는 DismissListener를 사용하면 된다.

하지만 그냥 사용하는것보다 약간의 튜닝을 하면 더 편하다.


public abstract class DialogDismissListener implements DialogInterface.OnDismissListener {

private HashMap<String, String> mStrMap;
private HashMap<String, Integer> mIntMap;
private HashMap<String, Boolean> mBoolMap;

public void setValue(String key, String value) {
if(mStrMap == null)
mStrMap = new HashMap<String, String>();

mStrMap.put(key, value);
}

public void setValue(String key, int value) {
if(mIntMap == null)
mIntMap = new HashMap<String, Integer>();

mIntMap.put(key, value);
}

public void setValue(String key, boolean value) {
if(mBoolMap == null)
mBoolMap = new HashMap<String, Boolean>();

mBoolMap.put(key, value);
}

public String getValueForStr(String key) {
if(mStrMap == null)
return null;
else
return mStrMap.get(key);
}

public int getValueForInt(String key, int defaultvalue) {
if(mIntMap == null)
return defaultvalue;
else {
if(mIntMap.get(key) == null)
return defaultvalue;
else
return mIntMap.get(key);
}
}

public boolean getValueForBool(String key, boolean defaultvalue) {
if(mBoolMap == null)
return false;
else {
if(mBoolMap.get(key) == null)
return defaultvalue;
else
return mBoolMap.get(key);
}
}
}


이렇게 DismissListener를 확장해서 추상클래스를 만든다

전달 객체는 입맛따라 추가하면 된다

여기서는 기본적인 String, int, boolean 만 넣었다




public class CustomDialogFragment extends DialogFragment {

public static final String KEY_RESULT = "Result";

private DialogDismissListener mResultListener;


@Override

public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {

    View bgview = inflater.inflate(R.layout.userlist, container, false);

bgview.findViewById(R.id.Button).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {

//결과값 전달

if (mResultListener != null)
mResultListener.setValue(KEY_RESULTtrue);

dismiss();
}
});

 return bgview;
}

@Override
public void onActivityCreated(Bundle arg0) {
super.onActivityCreated(arg0);

    //받은 리스너를 등록한다
getDialog().setOnDismissListener(mResultListener);
}

@Override
public void onCancel(DialogInterface dialog) {
super.onCancel(dialog);
    //반드시 여기서도 종료를 해주어야 한다. DismissListener를 등록할 경우 back키를 누르면 종료되지 않고 cancel만 되기 때문이다
dismiss();
}


//리스너를 받는다

public void setDismissListener(DialogDismissListener listener) {

    mResultListener = listener;
    }


}


위와 같이 DialogFragment를 적용한다




public boolean showDlg() {

CustomDialogFragment fragment = new CustomDialogFragment();
fragment.setDismissListener(new CustomDismissListener()); //리스너 넣기

fragment.show(getSupportFragmentManager(), "dialog");

}

public class CustomDismissListener extends DialogDismissListener {

@Override
public void onDismiss(DialogInterface dialog) {
if(getValueForBool(NameDialogFragment.KEY_RESULT, false)) {

//결과값 처리


}
}
};


DialogFragment를 호출할때 리스너 클래스를 만들고 객체를 생성해 집어 넣는다





 

 



수제 앱 장인: 고영진


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

  

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

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

 




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