블로그 이미지
매몰

모바일 어플리케이션 개발 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호 | 사이버몰의 이용약관 바로가기
개발/php, javascript 2018.11.26 10:09

round()를 5의 배수로도 반올림 해보자



왜 반올림은 0으로만 할까?

0말고 다른수로도 반올림 해보는건 어떨까?


라는 생각을 가끔씩 해봤는데.. 솔직히 별 필요성을 느끼지 못했었다.

그런데 주식 관련한 로보어드바이저를 만들면서 필요해졌다. 호가를 찍을때 5단위가 되기 때문이다.


아래와 같이 php의 반올림 함수인 round()를 이용하여 5의 배수로 반올림 해주는 코드를 짜봤다.


원리는 굳이 설명안해도 될만큼 간단하다. 여기서 볼것은 round() 함수의 두번째 인수인 반올림 자릿수도 고려한다는 사실이다. 이러한 편리한 기능은 살려놔야 코딩하는 재미가 있다ㅎㅎ




function roundCenter($price, $precision) {

//지정한 자릿수로 반올림 (1)

$round = round($price, $precision);

//지정한 자릿수 이하가 5가 되도록 변경 (2)

$square = -$precision;

$digit = pow(10, $square);

$center = (floor($price / $digit) * $digit) + (5 * pow(10, $square -1));

//(1)과 (2)를 원래 숫자와 비교하여 더 가까운 것을 택함

return abs($price - $round) < abs($price - $center) ? $round : $center;

}




결과값으로 검증해 보자...


echo roundCenter(573424.2517, 3).'<br />';

echo roundCenter(573424.2517, 2).'<br />';

echo roundCenter(573424.2517, 1).'<br />';

echo roundCenter(573424.2517, 0).'<br />';

echo roundCenter(573424.2517, -1).'<br />';

echo roundCenter(573424.2517, -2).'<br />';

echo roundCenter(573424.2517, -3).'<br />';




출력...


573424.2515
573424.25
573424.25
573424.5
573425
573400
573500




위에서 보듯이 지정 자릿수가 3이면 소수점 3째짜리까지 나오거나 소수점 4째자리가 5인 수가 나온다.




수제앱장인


 

 

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호 | 사이버몰의 이용약관 바로가기
개발/ios 2017.05.30 16:08

UIButton 이미지를 손쉽게 정렬하자~

아이폰 Swift에서 UIButton 안의 이미지를 원하는 위치에 두려면 

Edge Inset을 이용하여 좌우상하에 여백을 줘야 한다


여간 귀찮고 불편한 일이 아닐 수 없다


그래서 좀 더 편하게 이미지를 옮기는 방법을 소개한다


원리는 무지 간단하다

Edge Inset를 가로,세로 기준으로 이미지 크기와 함께 각각 계산해 주면 된다


왼쪽 정렬 

(L: 왼쪽 여백, R: 오른쪽 여백, M: 여백, IW: 이미지 너비, BW: 버튼 너비)





가운데 정렬 





이 두가지 경우만 이용하면 나머지 정렬도 모두 가능하다


코딩하면 다음과 같다

 


class ImageButton: UIButton {

    

    //가로 정렬 기준 (-1: 왼쪽, 0: 가운데, 1: 오른쪽)

    @IBInspectable var horizontal: Int = 0

    

    //세로 정렬 기준 (-1: , 0: 가운데, 1: 아래)

    @IBInspectable var vertical: Int = 0

    

    //기준으로부터의 거리 (여백)

    @IBInspectable var point: CGPoint = CGPointMake(0, 0)

    

    //이미지 크기

    @IBInspectable var size: CGSize = CGSizeMake(0, 0)

    

    private var mSetSize: CGSize?

    

    override func drawRect(rect: CGRect) {

        let width = frame.width

        let height = frame.height

        

        //처음 또는 버튼 크기가 달라지면 다시 위치 계산

        if  mSetSize == nil || !(mSetSize?.width == width && mSetSize?.height == height) {

            locate()

            

            //버튼 크기값 저장

            mSetSize = CGSizeMake(width, height)

        }

    }

    

    //위치 계산후 적용

    private func locate() {

        //가로 정렬

        let width = size.width

        let x1 = point.x

        let x2 = frame.width - x1 - width

        switch (horizontal) {

        case -1: //왼쪽 정렬

            

            //이미지 왼쪽은 여백 그대로

            imageEdgeInsets.left = x1

            

            //이미지 오른쪽은 버튼 너비에서 margin, 이미지 너비를 뺀다

            imageEdgeInsets.right = x2

            

        case 1: //오른쪽 정렬


            //왼쪽 정렬과 반대로 한다

            //이미지 왼쪽은 버튼 너비에서 margin, 이미지 너비를 뺀다

            imageEdgeInsets.left = x2

            

            //이미지 오른쪽은 여백 그대로

            imageEdgeInsets.right = x1

            

        default: //가운데 정렬

            

            //이미지 왼쪽은 버튼의 가운데에서 여백 더하고 다시 이미지 너비의 절반을 뺀다

            imageEdgeInsets.left = ((frame.width / 2) + x1) - (width / 2)

            

            //이미지 오른쪽은 버튼 너비에서 위에서 계산한 왼쪽 좌표와 이미지 너비를 뺀다

            imageEdgeInsets.right = frame.width - (imageEdgeInsets.left + width)

        }

        

        //세로 정렬 (가로 정렬과 같은 원리로)

        let height = size.height

        let y1 = point.y

        let y2 = frame.height - y1 - height

        switch (vertical) {

        case -1: // 정렬

            

            imageEdgeInsets.top = y1

            imageEdgeInsets.bottom = y2

            

        case 1: //아랫 정렬

            

            imageEdgeInsets.top = y2

            imageEdgeInsets.bottom = y1

            

        default: //가운데 정렬

            

            imageEdgeInsets.top = ((frame.height / 2) + y1) - (height / 2)

            imageEdgeInsets.bottom = frame.height - (imageEdgeInsets.top + height)

        }

    }

}





스토리 보드로 적용해 보자






Custom Class를 위에서 만든 Image Button으로 바꾸고

다음과 같이 입력해 보자







실행하면 이런 모습이다 

버튼안의 이미지가 50x50 이고

가로는 왼쪽에서 20포인트, 세로는 가운데에서 10포인트 움직였다






 

 



수제 앱 장인: 고영진


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

  

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

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

 



사업자 정보 표시
고영진모바일 | 고영진 | 서울특별시 관악구 낙성대동 서울대연구공원 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호 | 사이버몰의 이용약관 바로가기
TOTAL 69,579 TODAY 8