반응형

(210518 수정)

 

앱이 실행되고 있다고 할지라고 반드시 화면이 보이는 것은 아니다.

예를 들어, 음악을 재생한 경우 앱이 실행은 되어있지만 소리만 나오고 화면은 보이지 않는다.

이것은 화면없이 백그라운드(배경)에서 실행되는 '서비스(Service)' 가 있기 때문이다.

 

서비스도 앱의 구성 요소(컴포넌트)이므로 시스템에서 관리한다.

그러므로 매니페스트에 반드시 등록을 해줘서 시스템이 알 수 있게 해줘야한다.

 

그리고 서비스는 단말이 항상 실행되어 있는 상태로써,

다른 단말과 데이터를 주고받거나 필요한 기능을 백그라운드에서 실행하는데

그 실행된 상태를 유지하기 위해 서비스가 비정상적으로 종료가 되더라도 

시스템이 자동으로 재실행하게 된다.

 

서비스를 실행하려면 액티비티의 startService() 메소드에 인텐트 객체를 파라미터로 담아서 호출해야한다.

이 인텐드 객체에는 어떠한 서비스를 실행할 것인지, 어떤 부가 데이터를 넣은 것인지에 대한 정보를

담고 있으며, 시스템은 서비스를 실행시킨 후 인텐트 객체를 서비스에 전달하게된다.

 

그러나 startService() 메소드를 한 번이상 여러번 호출해서

서비스가 이미 메모리에 만들어진 상태로 유지되는 경우에는

서비스가 시작하는 목적 이외에 인텐트를 전달하는 목적으로도 사용된다.

이러한 경우에는 시스템이 onCreate() 가 아니라 onStartCommand() 메소드를 실행하게된다.

 

즉, onStartCommand() 메소드는 서비스로 전달된 인텐트 객체를 처리하는 메소드라는 것이다.

 

 

이번 시간에는 버튼을 누르면 입력창에 입력한 문자열이 서비스의 이름(데이터)이 되어 

서비스로 인텐트를 전달하게 되고. 그 서비스는 인텐트로 받은 데이터를 로그로 출력하고,

그 데이터를 다시 액티비티로 인텐트를 넘겨서 액티비티에서는 토스트 메시지로 데이터를 출력하는 기능을

만들어볼 것이다.

 

초기 화면

 

다음과 같이 간단하게 화면을 구성하였다.

자바 클래스 파일은

데이터를 인텐트에 담아서 주고 받아야 하기 때문에

액티비티 클래스 하나와 서비스 클래스 하나가 필요하다.

 

먼저 MainActivity.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
public class MainActivity extends AppCompatActivity {
 
    EditText edt;
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
 
        edt = (EditText)findViewById(R.id.edt);
 
        Button btn = (Button)findViewById(R.id.btn);
        btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                String name = edt.getText().toString(); // EditText에 입력한 값을 가져와서 name에 할당
 
                Intent intent = new Intent(getApplicationContext(), SendService.class); // 인텐트를 생성하고 부가 데이터 넣기
                intent.putExtra("name", name); // EditText에 입력한 값 name을 인텐트에 넣음
 
                startService(intent); // 인텐트를 담아서 서비스를 시작시킴(SendService 클래스로 전달)
            }
        });
 
        // 메인 액티비티가 메모리에 만들어져 있지 않은 상태에서 새로 만들어질 때 전달된 인텐트 처리
        Intent passedIntent = getIntent(); // 인텐트 객체를 가져옴
        processIntent(passedIntent);
    }
 
    public void onNewIntent(Intent intent) { // 액티비티가 이미 만들어져 있을 때 전달된 인텐트 처리
        processIntent(intent);
        super.onNewIntent(intent);
    }
 
    public void processIntent(Intent intent) {
        if(intent != null) {
            String name = intent.getStringExtra("name"); // 인텐트에서 name 키를 가져와서 name에 할당
 
            Toast.makeText(this, name + "을/를 받았습니다. ", Toast.LENGTH_SHORT).show();
        }
    }
}
cs

그리고 SendService.java

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
public class SendService extends Service { // 서비스를 상속해야함
    private static final String TAG = "SendService"// 로그 메시지 구분 태그
 
    public SendService() {
    }
 
    @Override
    public void onCreate() {
        super.onCreate();
        Log.d(TAG, "onCreate() 호출됨. ");
    }
 
    @Override // 인텐트를 여기서 처리함
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.d(TAG, "onStartCommand() 호출됨. ");
 
        if(intent == null) {
            return Service.START_STICKY; // 서비스가 강제종료되면 시스템이 서비스의 인텐트 값을 null로 초기화해서 재시작시킴
        } else {
            processCommand(intent);
        }
        return super.onStartCommand(intent, flags, startId);
    }
 
    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }
 
    private void processCommand(Intent intent) {
        // 인텐트에서 부가 데이터 가져오기
        String name = intent.getStringExtra("name"); // 인텐트에서 name 이라는 키의 값을 가져와서 name에 할당
 
        Log.d(TAG, "서비스 이름 : " + name ); // 버튼을 누르면 입력정보가 로그로 출력됨
 
        for(int i = 5; i >= 0; i--) {
            try {
                Thread.sleep(1000);
            } catch (Exception e) {
            };
            Log.d(TAG, i + "초만 기다리세요. ");
        }
 
        Intent showIntent = new Intent(getApplicationContext(), MainActivity.class); // 액티비티를 띄우기 위한 인텐트 객체 생성
        // 생성한 인텐트에 플래그 추가
        showIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | // 새로운 태스크(화면이 없는 서비스에서 화면이 있는 액티비티를 띄우기위함
                Intent.FLAG_ACTIVITY_CLEAR_TOP | // 액티비티 위의 다른 액티비티 제거
                Intent.FLAG_ACTIVITY_SINGLE_TOP); // 객체가 이미 메모리에 만들어져 있을 때 재사용
        showIntent.putExtra("name""서비스에서 보낸 " + name); // 인텐트에 name을 넣음
        startActivity(showIntent);
    }
 
    @Override
    public void onDestroy() {
        super.onDestroy();
    }
}
 
cs

그리고 매니페스트에 서비스를 등록해줘야 실행이 가능하다.

 

<service android:name=".SendService" />

 

이렇게 모두 작성하고

서비스의 이름을 입력해본다.

입력창에 '서비스테스트'라고 입력하게되면

액티비티에서 서비스로 인텐트가 전달되기 때문에

다음과 같이 로그가 출력이 된다.

 

서비스로 인텐트가 전달됨

그리고 "0초만 기다리세요" 라고 뜨는 순간

서비스에서 액티비티로 인텐트가 전달되기 때문에

다음과 같은 토스트 메시지가 출력이 된다.

 

다시 액티비티로 인텐트가 전달됨

 

이렇게 액티비티와 서비스가 인텐트에 데이터를 담아서

서로 주고 받는 기능을 알아보았다.

서비스는 앱에서 아주 많이 쓰이는 기능으로써

꼭 확실하게 알아두도록 하자.

 

피드백은 언제나 환영입니다.

 

반응형
반응형

(210518 수정)

 

지금까지 해왔던 방식과는 조금 다르게 화면에다 그래픽을 그려보는 방법을 알아보겠다.

표준 자바에서와 비슷하게 안드로이드의 뷰도 거의 같은 방식으로 그래픽을 사용하기 때문에

크게 어렵진 않을 것이다.

 

하지만 둘의 큰 차이점은 표준 자바에서는 Graphics 객체를 사용했다면,

안드로이드에서는 Canvas 객체에 그래픽을 그려야한다는 것이다.

 

이번 시간에는 그리기 속성을 담고 있는 '페인트(Paint)' 객체와

'캔버스(Canvas)' 객체을 사용해서 그래픽을 그리는 방법을 알아보겠다.

 

그래픽 그리기는 크게 4단계로 나눠볼 수가 있는데, 

 

1. 새로운 클래스 생성 후 View 상속

2. Paint() 객체 초기화 후 필요한 속성 설정

3. onDraw() 메소드 내에 그래픽을 그리는 메소드 호출

4. : 지금까지 만든 View를 메인 액티비티에 추가

 

간단히 말해서

View 클래스를 상속하고 이 View의 onDraw() 메소드를 이용해서 그래픽을 그려주면 된다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public class CustomView extends View { // 1. View 상속하기
 
    private Paint paint;
 
    public CustomView(Context context) {
        super(context);
 
        init(context);
        setBackgroundColor(Color.BLACK); // 배경색을 검은색으로
    }
 
    public CustomView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
 
        init(context);
    }
 
    private void init(Context context){
        paint = new Paint(); // 2. Paint 객체 생성하기
        paint.setColor(Color.YELLOW); // Paint 객체를 노란색으로
        // paint.setStyle(Paint.Style.FILL); // 노란색 사각형 채우기(디폴트 값) -> 현재 적용
        // paint.setStyle(Paint.Style.STROKE); // 노란색 사각형 그리기 -> 선만 노란색으로
    }
 
    @Override
    protected void onDraw(Canvas canvas) { // 3. onDraw() 메소드 정의 -> 여기서 원하는 그래픽을 그리면됨
        super.onDraw(canvas);
 
        canvas.drawRect(100,100,200,200,paint); // 사각형 그리기
        canvas.drawLine(400,400,1000,1000,paint); // 선 그리기
        canvas.drawCircle(3001200300, paint); // 원 그리기
    }
}
cs

 

1
2
3
4
5
6
7
8
9
10
11
12
13
public class MainActivity extends AppCompatActivity {
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
 
        // 4. 메인 액티비티에 추가하기
        CustomView customView = new CustomView(this);
        setContentView(customView);
    }
}
 
cs

 

 

사각형/선/원 그래픽

위와 같이 그래픽들을 간단하게 그려보았다.

 

추가적으로 그래픽을 그리는 데에 필요한 클래스와 메소드들을 몇 가지 더 알아보자.

 

클래스
캔버스(Canvas) View 화면에 직접 그래픽을 그릴 수 있게 해주는 객체로서
그래픽 그리기 메소드(onDraw())가 정의되어있다.
페인트(Paint) 그래픽을 그리기 위한 색상, 투명도, 폭, 스타일 등의 속성을
담고 있다.
비트맵(Bitmap) 픽셀로 구성된 이미지로서 메모리에 그래픽을 그리는 데 사용된다.
드로어블 객체(Drawable) 그래픽 요소가 객체로 정의되어 있다.

 

[Canvas 클래스가 제공하는 그리기 메소드]

 

drawPoint(float x, float y, Paint paint) : 좌표에 점을 그린다.

 

drawLine(float startX, float startY, float stopX, float stopY, Paint paint) : 시작점부터 끝점까지 직선을 그린다.

 

drawRect(float left, float top, float right, float bottm, Paint paint)

: 좌측 상단(left, top), 우측 하단(right, bottm)인 사각형을 그린다.

 

drawCircle(float cx, float cy, float radius, Paint paint) : 중심이 (cx, cy)이고 반지름이 radius인 원을 그린다.

 

drawText(String test, float x, float y, Paint paint) : 좌표에 텍스트를 그린다.

 

drawRoundRect(Rect rect, float rx, float ry, Paint paint)

: 둥근 사각형을 그리는데 Rect는 사각형 객체이고, rx와 ry는 사각형 모서리 부분의 반지름이다.

 

drawOval(RectF oval, Pain paint)

: oval에 내접하는 타원을 그리는데 paint의 스타일에 따라서 색이 채워지거나 외곽선만 그려진다.

 

drawArc(RectF oval, float startAngle, float sweepAngle, boolean useCenter, Paint paint)

startAngle에서 sweepAngle까지 oval안에 원호를 그리는데

시계방향으로 증가하며, sweepAngle이 360도보다 크면 타원이 전부 그려진다.

 

drawColor(int color) : Canvas의 전체 Bitmap을 원하는 색상으로 채운다.

 

[Paint 클래스가 제공하는 그리기 메소드]

 

void setColor(int color) : 포어그라운드 색을 설정한다.

 

int getColor() : 현재 포어그라운드 색을 설정한 색상을 반환한다.

 

void setAlpha(int a) : 투명도를 나타낸다.(0~255 사이의 값으로)

 

setStrokeWidth(float width) : 선의 폭(두께)을 설정한다.

 

void setStyle(Paint.Style style) : 선의 스타일을 지정한다.

-FILL : 도형의 내부를 채운다(디폴트 값)

-FILL_AND_STROKE : 도형의 내부를 채우면서 외곽선도 그린다.

-STROKE : 도형의 외곽선만 그린다. (내부는 채우지 않음)

 

setAntiAlias(true) : 부드러운 선으로

 

이렇게 그래픽을 그릴 수 있는 클래스와 메소드에 대해서도 몇 가지 알아보았다.

자신만의 그래픽을 만들어서 앱을 더 멋지게 꾸며보자.

 

 

피드백은 언제나 환영입니다.

 

 

 

 

 

반응형
반응형

(210518 수정)

 

우리는 어떤 앱을 이용하든지 프로그래스바를 자주 볼 수 있다.

이 프로그래스바와 비슷한 '시크바(SeekBar)'는 프로그래스바를 상속했으며

프로그래스바의 속성을 그대로 사용할 수 있다.

 

추가적으로, 사용자가 핸들을 드래그하여 좌우로 이동하며

값을 직접 조절할 수 있다.

 

우리는 보통 시크바를 스마트폰 단말의 화면 밝기라든지 음량 조절에서 볼 수 있다.

 

이번에는 시크바를 이용하여 화면 밝기를 조절하는 기능을 구현해볼건데,

화면에 시크바를 놓고 시크바 조정에 따른 값을 시크바 밑의 텍스트뷰로 출력할 것이다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?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:orientation="vertical"
    android:gravity="center">
 
    <SeekBar
        android:id="@+id/seekBar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:max="100" />
 
    <TextView
        android:id="@+id/tv"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="20dp"
        android:text="밝기 조절"
        android:gravity="center"
        android:textSize="30sp" />
 
</LinearLayout>
cs

다음과 같이 XML 레이아웃 파일을 작성하게되면

 

시크바 초기 화면

이렇게 간단하게 만들 수 있다.

 

다음으로는 기능을 담당할 액티비티 화면을 만들어보겠다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
public class MainActivity extends AppCompatActivity {
    
    TextView tv;
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
 
        tv = (TextView)findViewById(R.id.tv);
        SeekBar seekBar = (SeekBar)findViewById(R.id.seekBar);
        seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { // 시크바의 값이 바뀔 때마다 리스너가 알려줌
            @Override
            public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { // 시크바의 값이 바뀔 때마다 호출됨
                Brightness(progress);
                tv.setText("화면 밝기 : " + progress); // progress로 밝기를 조절해서 텍스트뷰에 출력함
            }
 
            @Override
            public void onStartTrackingTouch(SeekBar seekBar) {
 
            }
 
            @Override
            public void onStopTrackingTouch(SeekBar seekBar) {
 
            }
        });
    }
 
    private void Brightness(int value) { 
        if(value < 10) {
            value = 10;
        } else if (value > 100) {
            value = 100;
        }
 
        // 윈도우 매니저로 화면 밝기 설정
        WindowManager.LayoutParams params = getWindow().getAttributes(); // 참조한 객체의 윈도우 정보를 확인/설정 할 수 있음
        params.screenBrightness = (float)value/100;
        getWindow().setAttributes(params);
    }
}
cs

 

이렇게 시크바를 움직임으로써 화면 밝기를 조절할 수 있다.

 

피드백은 언제나 환영입니다.

반응형
반응형

(210522 수정)

 

데이터베이스는 앱의 데이터를 저장할 수 있다.

그렇기에 우리가 앱을 더 편리하고 원활하게 사용할 수 있는데,

그만큼 복잡할 수 있고, 사용자들의 중요한 데이터들을 관리하기에 유지보수가

상당히 중요하다고 생각한다.

 

이번 시간은 간단하게 데이터베이스의 생성과 

데이터베이스 내에 있는 테이블의 생성을 알아볼 것이고,

그 안에 사용자의 데이터를 담아서 삽입하고

삽입한 데이터를 조회해보는 기능을 구현해볼 것이다.

 

먼저, 가장 중요한 데이터베이스부터 만들어보자.

데이터베이스를 생성하는 메소드는 openOrCreateDatabase() 인데

생성할 데이터베이스가 이미 있다면 생성이 아니라 오픈이 된다.

 

openOrCreateDatabase API

public abstract SQLiteDatabse openOrCreateDatabase(String name, int mode, 

SQLiteDatabase.CursorFactory factory)

 

파라미터가 3개인데

첫 번째는 데이터베이스들을 구분하는 데이터베이스 이름이고,

두 번째는 접근 범위를 나타내는 사용 모드로써

이번 시간에는 이 앱에서만 접근할 수 있는 

'MODE_PRIVATE' 를 사용할 것이다.

마지막은 null이 아닌 객체를 지정할 경우,

쿼리의 결과 값으로 반환되는 데이터를 참조하는 'Cursor'를 만들 수 있는 객체가 전달된다.

 

그리고 SQLiteDatabase 객체에서 가장 중요한 메소드 중의 하나는 execSQL() 이다.

데이터베이스를 만들고 SQL문을 실행할 때 사용된다.

 

SQL문은 낯설 수도 있는데, 안드로이드에서 사용하는 SQLiteDatabase에서는

사용하는 것들이 한정적이니 큰 걱정은 안 해도 될 것 같다.

 

이제, 데이터베이스부터 만들어보자.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class MainActivity extends AppCompatActivity {

EditText dbEdt; // 데이터베이스 생성 버튼
SQLiteDatabase database; // 데이터베이스 변수

EditText tbEdt; // 테이블 생성 버튼
TextView tv; // 문자열 출력 텍스트뷰
String tbName; // 테이블 이름

Button selectBtn; // 데이터 조회하기 버튼

 @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
 
        dbEdt = (EditText)findViewById(R.id.dbEdt); // 데이터베이스 생성하기 버튼
        Button dbBtn = (Button)findViewById(R.id.dbBtn);
        dbBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                String dbName = dbEdt.getText().toString(); // EditText에 입력한 값이 문자열 형태로 dbName 변수에 할당됨
                createDatabase(dbName);
            }
        });
cs
1
2
3
4
5
6
7
private void createDatabase(String name) { // 데이터베이스 생성하기 메소드
        println("createDatabase 호출됨.");
 
        database = openOrCreateDatabase(name, MODE_PRIVATE, null);
        println("데이터베이스를 생성함 : " + name);
    }
 
cs

여기서 잠깐!

println() 메소드는 메소드 호출 결과를 텍스트뷰에 출력한다.

( tv = (TextView)findByViewId(R.id.tv); )

데이터베이스 생성일 때 뿐만 아니라

앞으로도 자주 쓰일 호출 결과 문자열 출력 메소드이다.

1
2
3
public void println(String data) {
        tv.append(data + "\n"); // 텍스트뷰에 데이터들을 출력, setText가 아니고 append인 이유는 추가해서 출력하기 위함
    }
cs

이렇게 데이터베이스를 만들었으면

테이블을 만들어보자.

 

1
2
3
4
5
6
7
8
9
10
-----(onCreate() 내부)-------

tbEdt = (EditText)findViewById(R.id.tbEdt); // 테이블 생성하기 버튼
        Button tbBtn = (Button)findViewById(R.id.tbBtn);
        tbBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                tbName = tbEdt.getText().toString(); // EditText에 입력한 값이 문자열 형태로 tbName 변수에 할당됨 -> insertRecord()에 사용할 것이므로 전역변수로 할당함
                createTable(tbName);
                insertRecord();
            }
        });
cs

테이블 생성 버튼을 누르면 테이블 생성과 함께

샘플 데이터도 삽입이 된다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
private void createTable(String name) { // 테이블 생성하기 메소드
        println("createTable 호출됨.");
 
        if(database == null) { // 데이터베이스가 널 이면
            println("데이터베이스부터 먼저 생성하세요. ");
            return;
        }
 
        String sql = "create table if not exists " + name + "("
                + " _id integer PRIMARY KEY autoincrement, "
                + " name text, "
                + " age integer, "
                + " sex text, "
                + " email text)";
 
        database.execSQL(sql); // 데이터베이스로 sql문을 실행해서 테이블 생성
        println("테이블을 생성함 : " + name);
    }
 
    private void insertRecord() { // 데이터 삽입 메소드
 
        println("insertRecord 호출됨. ");
        if(database == null) { // 데이터베이스가 널 이면
            println("데이터베이스부터 먼저 생성하세요. ");
            return;
        }
 
        if(tbName == null) { // 테이블 이름이 담긴 변수가 널 이면
            println("테이블부터 먼저 생성하세요. ");
            return;
        }
 
        database.execSQL("insert into " + tbName // 데이터베이스로 sql문을 실행해서 레코드를 삽입
        + " (name, age, sex, email)"
        + " values "
        + "('habilism', 25, 'Male', 'test@test.com')");
 
        println("레코드를 추가함. ");
    }
cs

데이터를 넣었으면 조회를 해서 사용자가 확인할 수 있도록 해야하기 때문에

조회하는 메소드도 만들어 보겠다.

 

1
2
3
4
5
6
7
--------(onCreate() 내부)-------------

selectBtn = (Button)findViewById(R.id.selectBtn); // 조회하기 버튼
        selectBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                dataSelect();
            }
        });
cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public void dataSelect() { // 삽입한 레코드를 조회하는 메소드
        println("dataSelect 호출됨. ");
 
        // Cursor 객체는 결과 테이블에 들어있는 각각의 레코드를 순서대로 접근할 수 있음
        Cursor cursor = database.rawQuery("select _id, name, age, sex, email from test"null); // SQL 실행하고 Cursor 객체 받기
        int recordCount = cursor.getCount(); // Cursor 객체로 레코드의 개수를 세서 recordCount 변수에 할당
        println("레코드 갯수 : " + recordCount);
 
        for(int i = 0; i < recordCount; i++) { // 레코드 갯수만큼 i를 반복
            cursor.moveToNext(); // 다음 결과 레코드로 넘어가기
 
            // 레코드들을 가져옴
            int id = cursor.getInt(0);
            String name = cursor.getString(1);
            int age = cursor.getInt(2);
            String sex = cursor.getString(3);
            String email = cursor.getString(4);
 
            println("조회한 레코드#" + i + " : " + id + ", " + name + ", " + age + ", " + sex + ", " + email);
        }
        cursor.close();
    }
cs

초기화면

여기서 또 잠깐! 입력창을 누르게 되면 키보드가 올라가면서 입력창이 키보드에 가려지게된다.

이것을 방지하기위한 코드를 매니페스트 파일에 추가해주자.

 

1
2
3
4
5
6
7
8
9
<activity android:name=".MainActivity"
            android:windowSoftInputMode="adjustPan">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
 
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
cs

이제 키보드가 나타나면 입력창이 위로 올라간다.
생성/삽입 후 조회 결과 

 

조회량이 많아지면 스크롤뷰에 의해서 스크롤이 된다.

피드백은 언제나 환영입니다.

반응형
반응형

(210518 수정)

 

앱을 사용하다보면 날짜와 시간을 설정해줘야 할 때가 있다.

설정할 때에는 Calendar 객체와 ~PickerDialog 객체가 있기 때문에

쉽게 날짜/시간을 설정하는 대화상자를 만들 수 있다.

 

날짜를 설정할 때에는 'DatePickerDialog',

시간을 설정할 때에는 'TimePickerDialog' 객체를 사용한다.

 

화면을 나타내는 레이아웃 파일은 단순하게 버튼 두개와 텍스트뷰 두개만 놓고 메인 액티비티 코드를 알아보자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
public class MainActivity extends AppCompatActivity {
 
    Button dateBtn;
    Button timeBtn;
    TextView dateTv;
    TextView timeTv;
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
 
        dateTv = (TextView)findViewById(R.id.dateTv);
        timeTv = (TextView)findViewById(R.id.timeTv);
 
        dateBtn = (Button)findViewById(R.id.dateBtn);
        dateBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                final Calendar c = Calendar.getInstance(); // Calendar 객체로 날짜 얻어오기
                int y = c.get(Calendar.YEAR);
                int m = c.get(Calendar.MONTH);
                int d = c.get(Calendar.DAY_OF_MONTH);
 
                // 날짜 대화상자 객체 생성
                DatePickerDialog datePickerDialog = new DatePickerDialog(MainActivity.thisnew DatePickerDialog.OnDateSetListener() {
                    @Override
                    public void onDateSet(DatePicker view, int year, int month, int dayOfMonth) {
                        dateTv.setText("날짜는 " + year + "년 " + (month + 1+ "월 " + dayOfMonth + "일 입니다. "); // 설정한 날짜를 텍스트뷰에 표시
                    } // month가 0~11로 되어 있기 때문에 1을 더해서 1~12로 만듬
                }, y, m, d);
                datePickerDialog.show();
            }
        });
 
        timeBtn = (Button)findViewById(R.id.timeBtn);
        timeBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                final Calendar c = Calendar.getInstance(); //  Calendar 객체로 시간 얻어오기
                int h = c.get(Calendar.HOUR_OF_DAY);
                int m = c.get(Calendar.MINUTE);
 
                TimePickerDialog timePickerDialog = new TimePickerDialog(MainActivity.thisnew TimePickerDialog.OnTimeSetListener() {
                    @Override
                    public void onTimeSet(TimePicker view, int hourOfDay, int minute) {
                        timeTv.setText("시간은 " + hourOfDay + "시 " + minute + "분 입니다. "); // 설정한 시간을 텍스트뷰에 표시
                    }
                }, h, m, false); // true이면 24시각제, false이면 12시각제(오전/오후)인데 텍스트뷰 표시할 때는 24시각제
                timePickerDialog.show();
            }
        });
    }
}
cs

매우 간단하게 두 가지 기능을 구현할 수 있다.

 

초기 화면
DatePickerDialog
TimePickerDialog
최종 설정 화면

 

 

피드백은 언제나 환영입니다.

반응형
반응형

(210518 수정)

 

이번 주제는 안드로이드 앱 개발을 할 때에 반드시 알고 있어야할

가장 중요하면서도 기초적인 생명주기(Life Cycle)(또는 수명주기, 생애주기)이다.

 

안드로이드 시스템은 실행되는 앱의 상태를 직접 관리하는데,

이렇게 시스템에 의해 직접 관리되지 않으면 실행된 앱이 과도하게 메모리를 점유하거나

화면을 보여주는 권한을 지나치게 할 수 있기 때문에 

앱이 가지고 있는 기능을 사용하지 못할 수 있는 아주 큰 문제를 초래한다.

 

우리가 앱을 사용하다 보면 앱을 도중에 갑작스럽게 종료하거나 다시 실행시키는 경우가 많다.

예를 들어, 유튜브로 영상을 시청하고 있다가 친구에게 카톡이 와서 

카톡화면으로 갑자기 이동하게 되면 유튜브 화면은 카톡화면 뒤로(background) 들어가서 중지될 수 있다.

 

이렇게 화면(액티비티)은 실행과 중지와 같은 여러가지 상태정보를

가지고 있는데, 이것들을 메소드로 호출하게 된다.

(우리가 잘 알고있는 대표적인 예로 onCreate() 콜백메소드가 있다.)

 

액티비티가 처음 생성된 후 소멸될 때까지 상태가 변하면서 

각각의 상태마다의 메소드가 실행된다는 말이다.

 

그 상태 메소드들을 알아보자.

안드로이드의 생명주기 상태 메소드(출허 : 안드로이드 디벨로퍼)

 

1. onCreate() : 우리가 잘 알고 있는 낯익은 메소드이다. 어디서 봤을까?

그냥 안드로이드 스튜디오에서 프로젝트만 생성해도 MainActivity.java에서 바로 찾아볼 수 있다.

액티비티가 처음에 만들어졌을 때 호출되는 콜백메소드로써

액티비티의 구성요소들을 초기화하는 부분이다.

이전 상태가 저장되어 있는 경우에는 번들(Bundle) 객체를 참조하여 이전 상태를 복원할 수 있다.

이 메소드 다음에는 반드시 onStart()가 실행된다.

 

2. onStart() : 액티비티가 화면에 보이기 바로 전에 호출되는 콜백 메소드이다.

액티비티가 화면에 보이게 되면 이 메소드 다음에 onResume() 메소드가 호출되고

화면에서 가려지게 되면 이 메소드 다음에 onStop() 메소드가 호출된다.

 

3. onResume() : 액티비티가 사용자와 상호작용하기 바로 전에 호출되는 콜백 메소드이다.

 

4. onPause() : 다른 액티비티가 시작하려고 할 때 호출되는 콜백 메소드로서

호출이 되고 나면 화면의 일시 정지 상태가 된다.

(사용자에게는 보이지만 기존 액티비티(보이긴함) 위에 다른 액티비티가 있어(대화상자가 일부를 가려)

포커스를 받지 못하는 상태이다.

 

5. onStop() : 액티비티가 다른 액티비티에 의해 완전히 가려져 더 이상 보이지 않을 때 호출되는 콜백 메소드이다.

 

6. onDestroy() : 액티비티가 완전히 소멸되어 없어지기 직전에 호출되는 콜백 메소드이다.

이 메소드가 액티비티가 받는 마지막 호출로써

액티비티가 앱에 의해 강제적으로 종료가 되거나 시스템이 강제로 종료시키는 경우에 호출될 수도 있다.

 

잘 알겠지만 모든 앱에서 이 메소드들을 모두 구현할 필요는 없다.

하지만 각 생애주기 콜백 메소드들이 언제 호출되는지 정확하게 알고 있어야 

여러 가지 상황에서 앱이 중지되는 것을 막을 수 있다.

 

그런데 만약에 게임을 한다든가 앱으로 중요한 작업을 하고 있을 때

갑자기 전화가 온다든가 카톡이 오면 작업을 잠시 중지하고 연락을 해야한다.

이렇게 중지되어도 다시 원래 상태로 되돌아가야 하는데,

그럴 때는 중지하기 전에 작업 중인 그 정보를 저장해두었다가 다시 실행되었을 때

그 상태부터 다시 시작할 수 있도록 만들어줘야한다.

이런 경우에 사용하는 중요한 메소드는 onPause()와 onResume() 이다.

 

앱이 갑자기 중지가 될 때 호출되는 onPause() 메소드로 앱의 상태를 저장하고

앱이 다시 실행이 될 때 onResume() 메소드로 그 상태를 복원하는 것이다.

 

피드백은 언제나 환영입니다.

반응형
반응형

(210517 수정)

 

카드뷰(CardView)는 FrameLayout을 확장한 형태로써 리스트 형식으로 보여주는데

둥근 모서리와 그림자의 추가로 레이아웃을 보다 더 예쁘고 깔끔하게 해줘서

많은 개발자들이 이용하고 있다.  (단순히 레이아웃이 카드 모양과 닮았다고 해서 카드뷰이다.)

 

일반적으로 리스트 형식으로 사용하니까

재사용이 가능한 RecyclerView를 사용하겠다.

 

우선 가장 중요한 카드 모양을 만들기 위한

속성부터 알아보겠다.

 

cardElevation : 카드의 그림자를 생성 및 설정

cardCornerRadius : 카드의 모서리를 둥글게 설정

cardBackGroundColor : 카드의 배경색을 설정

contentPadding : 카드 내부 간격을 설정

.....

 

속성들이 몇 개 더 있지만 주로 사용하는 것들로 구성해보았다.

 

이제 카드뷰를 만들어보자.

 

먼저, 카드뷰 라이브러리를 build.gradle Module:app에 추가한다.

1
2
3
4
5
dependencies {
 
    implementation 'androidx.cardview:cardview:1.0.0'
    implementation 'androidx.recyclerview:recyclerview:1.2.0'
}
cs

 

그리고나서 레이아웃을 만들건데 activity_main.xml에는 껍데기인 리사이클러뷰만 추가해놓고

카드를 만들 새 레이아웃 파일인 card_item.xml을 생성한다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
<?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="wrap_content"
    xmlns:card_view="http://schemas.android.com/apk/res-auto"
    android:orientation="horizontal">
 
    <androidx.cardview.widget.CardView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_margin="5dp"
        card_view:cardCornerRadius="20dp"
        card_view:cardElevation="15dp"
        card_view:contentPadding="10dp"
        card_view:cardBackgroundColor="@color/white">
 
        <RelativeLayout
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" >
 
            <ImageView
                android:id="@+id/cardImg"
                android:layout_width="match_parent"
                android:layout_height="200dp"
                android:src="@drawable/maple"
                android:scaleType="centerCrop" />
 
            <TextView
                android:id="@+id/cardTitle"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="이름"
                android:layout_below="@+id/cardImg"
                android:layout_marginTop="5dp"
                android:layout_marginLeft="5dp"/>
 
            <TextView
                android:id="@+id/cardContent"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="내용"
                android:layout_below="@+id/cardTitle"
                android:layout_marginBottom="5dp"
                android:layout_marginLeft="5dp" />
 
            <ImageView
                android:layout_width="20dp"
                android:layout_height="30dp"
                android:src="@android:drawable/ic_menu_delete"
                android:layout_below="@id/cardImg"
                android:layout_alignParentRight="true"
                android:layout_margin="5dp" />
        
        </RelativeLayout>
    </androidx.cardview.widget.CardView>
</LinearLayout>
cs

하나의 카드뷰 아이템

CardView 안에 RelativeLayout을 만들어서 

제목과 내용 반대쪽에 삭제이미지 버튼을 놓는다.

 

다음으로 카드 아이템의 데이터(이미지뷰, 제목, 내용)를 담고 있는 모델을 생성한다.

 

1
2
3
4
5
6
7
8
9
10
11
12
public class CardItem {
 
    public int imageView;
    public String title;
    public String content;
 
    public CardItem(int imageView, String title, String content) {
        this.imageView = imageView;
        this.title = title;
        this.content = content;
    }
}
cs

이제 껍데기인 리사이클러뷰를 어댑터를 통해서

화면에 보여지게 할 것이다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
public class RecyclerViewAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
 
    private ArrayList<CardItem> cardItems = new ArrayList<>(); // 카드 아이템을 담아주는 ArrayList
 
    public RecyclerViewAdapter() {
 
        //카드 아이템 추가
        cardItems.add(new CardItem(R.drawable.maple, "메이플스토리""미니게임"));
        cardItems.add(new CardItem(R.drawable.maple, "하빌리즘""카드뷰 기능 구현"));
        cardItems.add(new CardItem(R.drawable.maple, "인스타""#좋아요반사"));
        cardItems.add(new CardItem(R.drawable.maple, "메이플스토리""미니게임"));
        cardItems.add(new CardItem(R.drawable.maple, "하빌리즘""카드뷰 기능 구현"));
        cardItems.add(new CardItem(R.drawable.maple, "인스타""#좋아요반사"));
    }
 
    @NonNull
    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        // 생성되면서 자동으로 호출됨 -> card_item.xml을 인플레이션
        View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.card_item, parent, false);
 
        return new VierHolder(view); // 뷰홀더 객체를 생성하면서 뷰 객체를 전달하고 그 뷰홀더 객체를 반환
    }
 
    @Override // 뷰홀더가 재사용될 때 호출됨, 뷰 객체는 기존 것을 그대로 사용하고 데이터만 바꿔줌
    public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
 
       ((VierHolder)holder).cardImg.setImageResource(cardItems.get(position).cardImg);
       ((VierHolder)holder).cardTitle.setText(cardItems.get(position).cardTitle);
       ((VierHolder)holder).cardContent.setText(cardItems.get(position).cardContent);
 
    }
 
    @Override
    public int getItemCount() {
        return cardItems.size(); // 카드 아이템 리스트의 크기
    }
 
    private class VierHolder extends RecyclerView.ViewHolder {
 
        public ImageView cardImg;
        public TextView cardTitle;
        public TextView cardContent;
 
        public VierHolder(View view) { // 뷰홀더 생성자로 전달되는 뷰 객체를 참조함(아이템들은 뷰로 만들어지고, 뷰는 뷰홀더에 담아둠)
            super(view); // 이 뷰 객체를 부모 클래스의 변수에 담아둠
           cardImg= (ImageView)view.findViewById(R.id.cardImg);
           cardTitle= (TextView)view.findViewById(R.id.cardTitle);
           cardContent= (TextView)view.findViewById(R.id.cardContent);
        }
    }
}
 
cs

실질적인 리사이클러뷰 구현은 이 정도가 끝이고

이것을 MainActivity.java 안에 있는 껍데기 리사이클러뷰와 연결만 해주면 된다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class MainActivity extends AppCompatActivity {
 
    private ArrayList<CardItem> cardItems = new ArrayList<>();
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
 
        RecyclerView recyclerView = (RecyclerView)findViewById(R.id.recyclerView);
        recyclerView.setLayoutManager(new LinearLayoutManager(this));
        recyclerView.setAdapter(new RecyclerViewAdapter());
    }
cs

깔끔한 디자인의 카드뷰 완성

 

카드뷰로 무궁무진한 응용을 해보자.

 

피드백은 언제나 환영입니다.

반응형
반응형

(210517 수정)

 

진동과 소리는 앱을 사용하는 사용자에게 무언가를 알려주는 가장 간단한 방법이다.

모바일 게임이나 일상생활에서 자주 사용하는 앱에서 쉽게 알 수 있다.

 

진동 기능을 구현하려면 "바이브 레이터(Vibrator)" 라는 

시스템 서비스 객체를 사용해야한다.

이 객체의 "바이브 레이트(Vibrate)" 메소드로

진동이 울리는 시간과 음량을 설정할 수 있다.

 

먼저 진동이 가능하도록 매니페스트에 사용 권한을 주도록 하겠다.

<uses-permission android:name="android.permission.VIBRATE" />

 

이제 기능들을 구현해볼건데

이번에는 딱히 UI에는 신경 쓰지 않겠다.

단순하게 버튼을 누르면 스마트폰에서 진동이 일어나는 기능부터 알아보겠다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class MainActivity extends AppCompatActivity {
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
 
        Button vibe = (Button)findViewById(R.id.vibe);
        vibe.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Vibrator vibrator = (Vibrator)getSystemService(Context.VIBRATOR_SERVICE); // getSystemService를 이용해서 진동 기능을 가져와 Vibrator 객체를 참조함
 
                if(Build.VERSION.SDK_INT >= 26) { // 안드로이드 버전 26부터 vibrate 메소드의 파라미터가 변경되었으므로 if문으로 최신버전과 과거버전 모두 작성
                    vibrator.vibrate(VibrationEffect.createOneShot(1000, 20)); // 지속시간과 음량
                } else {
                    vibrator.vibrate(1000);
                }
            }
        });
}
}
cs

 

소리 기능을 구현하려면 "링턴(Ringtone)" 객체를 사용해서

API에서 제공하는 기본 소리를 재생할 수 있게 한다.

링턴 객체의 play() 메소드로 소리를 재생하는 것이다.

 

소리도 마찬가지로

버튼을 누르면 스마트폰에서 알람 소리가 나게 구현해볼 것이다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class MainActivity extends AppCompatActivity {
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
 
        Button sound = (Button)findViewById(R.id.sound);        
        sound.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Uri uri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION); // RingtoneManager를 통해 기본 알림 소리를 가져옴
                Ringtone ringtone = RingtoneManager.getRingtone(getApplicationContext(), uri); //  Uri 객체를 전달하면 가져온 소리는 Ringtone객체를 참조함 
                ringtone.play(); // 소리 실행
            }
        });
    }
}
cs

 

정말 간단하지만 본인 스마트폰으로 직접 실행시켜보길 바란다.

 

피드백은 언제나 환영입니다.

 

 

 

반응형

+ Recent posts