이번 포스트에서 간단하게 알람 앱을 구현해 보겠습니다. Adapter에 대해' 다시 한번 정리해 보자'라는 생각으로 작성한 포스트입니다.
이 앱은 시간을 설정했다고 알람이 울리거나 하지 않습니다. 이를 구현하려면 foreground와 background, 안드로이드 생명주기, 쓰레드 등 다양한 개념이 필요합니다. 특히 구글에서는 RecyclerView를 활용해서 개발하라고 추천하기 때문에 이번 포스트에선 앞서 말한 것처럼 Adapter에 대해 마무리를 짓는 생각으로 내용을 다뤄보겠습니다.(앞서 다뤘던 포스트와는 별 차이 없습니다.) 시작하겠습니다.
앞선 포스트에서도 등장했었지만 기본 레이아웃은 다음과 같습니다.
![]() 알람 리스트 |
![]() 시간 설정 |
MainActivity에서는 현재시간을 실시간(계속 바뀌는)으로 표시해주고, 알람 리스트를 추가 및 삭제하는 버튼과 알람 리스트를 출력하는 리스트가 존재합니다. TimePickerActivity에서는 시간을 셋팅해주는 TimePicker를 사용했습니다.
자 먼저 새로운 프로젝트를 하나 생성해 주시고, 레이아웃을 꾸미러 가보겠습니다. activity_main.xml 입니다.
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:id="@+id/current1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_horizontal|center_vertical"
android:paddingTop="70dp"
android:textSize="20dp"
android:text="현재시간" />
<TextView
android:id="@+id/current"
android:layout_width="match_parent"
android:layout_height="70dp"
android:gravity="center_vertical|center_horizontal"
android:textStyle="bold"
android:text="현재시간" />
</LinearLayout>
<RelativeLayout
android:id="@+id/relative"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal" >
<Button
android:id="@+id/addBtn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_toLeftOf="@+id/removeBtn"
android:text="+"/>
<Button
android:id="@+id/removeBtn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:text="-"/>
</RelativeLayout>
<ListView
android:id="@+id/list_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:listitem="@layout/round_theme">
</ListView>
</LinearLayout>
activity_main.xml입니다. 먼가 좀 Layout이 많아서 복잡해 보일 수도 있는데 천천히 보시면 어려운 layout은 아닙니다. 이제 activity_time_picker.xml로 넘어가 보겠습니다. 여기서 ListView에 tools를 저를 따라 하는 것이 아니라면 레이아웃 이름을 고치셔야 합니다.(따라 하시는 분들은 오류가 나도 계속 진행해 주세요.)
activity_time_picker.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".TimePickerActivity">
<RelativeLayout
android:id="@+id/time_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="90dp">
<TimePicker
android:id="@+id/time_picker"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:timePickerMode="spinner"
android:layout_centerInParent="true"/>
</RelativeLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center">
<Button
android:id="@+id/okBtn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="확인"/>
<Button
android:id="@+id/cancleBtn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="취소"/>
</LinearLayout>
</LinearLayout>
이 레이아웃은 TimePicker를 활용한 레이아웃입니다. TimePicker는 시간을 설정할 수 있는 아주 좋은 아이템입니다. 나중에 따로 한번 다뤄보겠습니다.
다음은 커스텀 레이아웃을 만들 차례입니다. 앞선 포스트에서 만드는 방법을 올려두었으니 참고해 주시기 바랍니다.
[Android] 안드로이드 리스트뷰 모서리 둥글게 만들기
안녕하세요. 이번 포스트에선 밑에 사진과 같이 ListView를 사용할 때 저렇게 모서리가 둥글둥글하고 이쁜 리스트뷰를 만들어 볼 겁니다. 자 먼저 res/drawable 폴더에 가셔서 layout_background.xml을 만
50billion-dollars.tistory.com
이제 만들었던 커스텀 레이아웃을 적용시켜볼 차례입니다. round_theme.xml입니다.
round_theme.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_height="match_parent"
android:layout_width="match_parent"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_margin="6dp"
android:padding="6dp"
android:background="@drawable/layout_background" >
<TextView
android:id="@+id/am_pm"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="오전/오후"
android:textAppearance="@style/TextAppearance.AppCompat.Small"/>
<TextView
android:id="@+id/textTime1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="몇시"
android:paddingLeft="10dp"
android:textAppearance="@style/TextAppearance.AppCompat.Large"/>
<TextView
android:id="@+id/textTime2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="몇분"
android:paddingLeft="10dp"
android:textAppearance="@style/TextAppearance.AppCompat.Large"/>
<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="match_parent">
<TextView
android:id="@+id/time_month"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="몇월"
android:layout_toLeftOf="@+id/time_day"
android:paddingRight="10dp"
android:textAppearance="@style/TextAppearance.AppCompat.Large"/>
<TextView
android:id="@+id/time_day"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="몇일"
android:layout_toLeftOf="@+id/switchBtn"
android:paddingRight="10dp"
android:textAppearance="@style/TextAppearance.AppCompat.Large"/>
<Switch
android:id="@+id/switchBtn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:focusable="false"
android:focusableInTouchMode="false"/>
</RelativeLayout>
</LinearLayout>
</LinearLayout>
여기서 weight를 사용하면 더 쉽게 구현할 수 있었을 거 같았는데 이건 다음에 개선점으로 남겨두겠습니다.
자 이제 전반적인 레이아웃 구성을 마치셨으면 이제 이 기능들을 만들러 가봅시다. 먼저 MainActivity입니다.
MainActivity
package hello.world.alamapplication;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import android.app.TimePickerDialog;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.view.View;
import android.widget.AdapterView;
import android.widget.Button;
import android.widget.ListView;
import android.widget.ProgressBar;
import android.widget.Switch;
import android.widget.TextView;
import android.widget.Toast;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
public class MainActivity extends AppCompatActivity {
public static final int REQUEST_CODE1 = 1000;
public static final int REQUEST_CODE2 = 1001;
private AdapterActivity arrayAdapter;
private Button tpBtn, removeBtn;
private ListView listView;
private TextView textView;
private int hour, minute;
private String month, day, am_pm;
private Handler handler;
private SimpleDateFormat mFormat;
private int adapterPosition;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
/*스위치를 포함한 커스텀 adapterView 리스트 터치 오류 관련 문제 해결(Java code)
switch.setFocusable(false);
switch.setFocusableInTouchMode(false);*/
arrayAdapter = new AdapterActivity();
listView = (ListView) findViewById(R.id.list_view);
listView.setAdapter(arrayAdapter);
//List에 있는 항목들 눌렀을 때 시간변경 가능
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
adapterPosition = position;
arrayAdapter.removeItem(position);
Intent intent = new Intent(MainActivity.this, TimePickerActivity.class);
startActivityForResult(intent,REQUEST_CODE2);
}
});
/*long now = System.currentTimeMillis();
Date date = new Date(now);*/
//쓰레드를 사용해서 실시간으로 시간 출력
handler = new Handler() {
@Override
public void handleMessage(@NonNull Message msg) {
Calendar cal = Calendar.getInstance();
mFormat = new SimpleDateFormat("HH:mm:ss");
String strTime = mFormat.format(cal.getTime());
textView = (TextView) findViewById(R.id.current);
textView.setTextSize(30);
textView.setText(strTime);
}
};
class NewRunnable implements Runnable {
@Override
public void run() {
while(true) {
try {
Thread.sleep(1000);
}catch (Exception e) {
e.printStackTrace();
}
handler.sendEmptyMessage(0);
}
}
}
NewRunnable runnable = new NewRunnable();
Thread thread = new Thread(runnable);
thread.start();
//TimePicker의 시간 셋팅값을 받기 위한 startActivityForResult()
tpBtn = (Button) findViewById(R.id.addBtn);
tpBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent tpIntent = new Intent(MainActivity.this, TimePickerActivity.class);
startActivityForResult(tpIntent, REQUEST_CODE1);
}
});
removeBtn = (Button) findViewById(R.id.removeBtn);
removeBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
arrayAdapter.removeItem();
arrayAdapter.notifyDataSetChanged();
}
});
}
//TimePicker 셋팅값 받아온 결과를 arrayAdapter에 추가
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);
//시간 리스트 추가
if(requestCode == REQUEST_CODE1 && resultCode == RESULT_OK && data != null) {
hour = data.getIntExtra("hour", 1);
minute = data.getIntExtra("minute", 2);
am_pm = data.getStringExtra("am_pm");
month = data.getStringExtra("month");
day = data.getStringExtra("day");
arrayAdapter.addItem(hour, minute, am_pm, month, day);
arrayAdapter.notifyDataSetChanged();
}
//시간 리스트 터치 시 변경된 시간값 저장
if(requestCode == REQUEST_CODE2 && resultCode == RESULT_OK && data != null) {
hour = data.getIntExtra("hour", 1);
minute = data.getIntExtra("minute", 2);
am_pm = data.getStringExtra("am_pm");
month = data.getStringExtra("month");
day = data.getStringExtra("day");
arrayAdapter.addItem(hour, minute, am_pm, month, day);
arrayAdapter.notifyDataSetChanged();
}
}
}
코드에 대략적인 주석을 달았으니 제 포스트를 읽으셨던 분들이 시라면 무리 없이 이해하실 수 있을 것 같습니다. 중간에 Thread를 사용한 코드가 보이실 텐데 이것은 화면에 실시간으로 시간을 출력해주는 함수입니다. 이 Thread도 나중에 한 번 다시 다뤄보도록 하겠습니다.
그리고 개발하면서 막혔던 부분이 바로 Switch입니다. 분명 리스트를 터치하는데도 Activity이동이 먹히질 않아 살짝 삽질 좀 했던 기억이 나는데요, 이는 리스트뷰를 터치해도 포커스가 이미 switch에 맞춰져 있어서 리스트 터치가 먹히질 않았던 것입니다. 이를 해결한 방법은 Java 코드와 xml 파일 모두 사용할 수 있습니다. 먼저 Java 코드입니다.
1. Java
switch.setFocusable(false);
switch.setFocusableInTouchMode(false);
스위치 객체를 생성한 다음 저렇게 작성하시면 됩니다.
2. XML
android:focusableInTouchMode="false"
스위치에 딱 이 한 줄 코드만 설정해 놓으시면 됩니다. 자바는 자바 소스에서 스위치 생성 시 주로 사용하는 방법입니다.
이것에 대해 더욱 자세히 알고 싶은 분은 제가 참고한 블로그를 올려둘 테니 참고하시기 바랍니다.
커스텀 리스트뷰에서 아이템 클릭(onItemClick)이 동작하지 않던 현상 해결방법
안녕하세요. Simple& Happy Dev입니다. 커스텀 리스트뷰(Custom ListView)를 가지고 "접근성 검사기를 이용한 접근성 개선 예제"를 만드는 과정에서 리스트뷰의 아이템를 클릭했지만 동작하지 않는 이슈��
happydev.kr
여기까지 완료됐다면 이제 model클래스를 작성해 보겠습니다. Time 액티비티를 하나 생성해 주시고 다음과 같이 만들어줍니다.
Time
package hello.world.alamapplication;
public class Time {
private int hour, minute;
private String month, day, am_pm;
public int getHour() {
return hour;
}
public void setHour(int hour) {
this.hour = hour;
}
public int getMinute() {
return minute;
}
public void setMinute(int minute) {
this.minute = minute;
}
public String getAm_pm() {
return am_pm;
}
public void setAm_pm(String am_pm) {
this.am_pm = am_pm;
}
public String getMonth() {
return month;
}
public void setMonth(String month) {
this.month = month;
}
public String getDay() {
return day;
}
public void setDay(String day) {
this.day = day;
}
@Override
public String toString() {
final StringBuffer sb = new StringBuffer("Time{");
sb.append("hour=").append(hour);
sb.append(", minute=").append(minute);
sb.append('}');
return sb.toString();
}
}
이거 쉽게 만드는 법은 [ Alt + Insert ] 모두 기억하시죠? 기억이 안 나거나 모르시는 분은 BaseAdapter(2) 편을 보시고 오면 되겠습니다.
여기서 toString()은 나중에 디버깅할 때 로그 창에 표시할 수 있어 값이 제대로 전달됐는지 확인하기 위해 만들어 봤습니다. 이것도 위와 같은 방법으로 하면 자동으로 생성됩니다.
이제 TimePickerActivity 액티비티를 생성해주세요
TimePickerActivity
package hello.world.alamapplication;
import androidx.appcompat.app.AppCompatActivity;
import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.TimePicker;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.Locale;
public class TimePickerActivity extends AppCompatActivity {
private TimePicker timePicker;
private Button okBtn, cancelBtn;
private int hour, minute;
private String am_pm;
private Date currentTime;
private String stMonth, stDay;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_time_picker);
timePicker = (TimePicker)findViewById(R.id.time_picker);
currentTime = Calendar.getInstance().getTime();
SimpleDateFormat day = new SimpleDateFormat("dd", Locale.getDefault());
SimpleDateFormat month = new SimpleDateFormat("MM", Locale.getDefault());
stMonth = month.format(currentTime);
stDay = day.format(currentTime);
okBtn = (Button)findViewById(R.id.okBtn);
okBtn.setOnClickListener(new View.OnClickListener() {
//안드로이드 버전별로 시간값 세팅을 다르게 해주어야 함. 여기선 Android API 23
@Override
public void onClick(View v) {
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
hour = timePicker.getHour();
minute = timePicker.getMinute();
}
else {
hour = timePicker.getCurrentHour();
minute = timePicker.getCurrentMinute();
}
am_pm = AM_PM(hour);
hour = timeSet(hour);
Intent sendIntent = new Intent(TimePickerActivity.this, MainActivity.class);
sendIntent.putExtra("hour", hour);
sendIntent.putExtra("minute", minute);
sendIntent.putExtra("am_pm", am_pm);
sendIntent.putExtra("month", stMonth);
sendIntent.putExtra("day", stDay);
setResult(RESULT_OK, sendIntent);
finish();
}
});
//취소버튼 누를 시 TimePickerAcitivity 종료
cancelBtn = (Button) findViewById(R.id.cancleBtn);
cancelBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
finish();
}
});
}
//24시 시간제 바꿔줌(군대도 아니고..)
private int timeSet(int hour) {
if(hour > 12) {
hour-=12;
}
return hour;
}
//오전, 오후 선택
private String AM_PM(int hour) {
if(hour >= 12) {
am_pm = "오후";
}
else {
am_pm = "오전";
}
return am_pm;
}
}
여기도 적당한 주석을 달았으니 모르는 것이 있으면 질문해주세요.
마지막으로 Adapter를 만들어 봅시다. AdapterAcitivty입니다.
AdapterAcitivty
package hello.world.alamapplication;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.TextView;
import java.util.ArrayList;
public class AdapterActivity extends BaseAdapter {
public ArrayList<Time> listviewitem = new ArrayList<Time>();
private ArrayList<Time> arrayList = listviewitem; //백업 arrayList
@Override
public int getCount() {
return arrayList.size();
}
@Override
public Object getItem(int position) {
return arrayList.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder;
if(convertView == null) {
holder = new ViewHolder();
convertView = LayoutInflater.from(parent.getContext()).inflate(R.layout.round_theme, parent, false);
TextView hourText = (TextView)convertView.findViewById(R.id.textTime1);
TextView minuteText = (TextView)convertView.findViewById(R.id.textTime2);
TextView am_pm = (TextView)convertView.findViewById(R.id.am_pm);
TextView month = (TextView)convertView.findViewById(R.id.time_month);
TextView day = (TextView)convertView.findViewById(R.id.time_day);
holder.hourText = hourText;
holder.minuteText = minuteText;
holder.am_pm = am_pm;
holder.month = month;
holder.day = day;
convertView.setTag(holder);
}
else {
holder = (ViewHolder)convertView.getTag();
}
Time time = arrayList.get(position);
holder.am_pm.setText(time.getAm_pm());
holder.hourText.setText(time.getHour()+ "시");
holder.minuteText.setText(time.getMinute()+ "분");
holder.month.setText(time.getMonth()+ "월 ");
holder.day.setText(time.getDay()+ "일");
return convertView;
}
public void addItem(int hour, int minute, String am_pm, String month, String day) {
Time time = new Time();
time.setHour(hour);
time.setMinute(minute);
time.setAm_pm(am_pm);
time.setMonth(month);
time.setDay(day);
listviewitem.add(time);
}
//List 삭제 method
public void removeItem(int position) {
if(listviewitem.size() < 1) {
}
else {
listviewitem.remove(position);
}
}
public void removeItem() {
if(listviewitem.size() < 1) {
}
else {
listviewitem.remove(listviewitem.size()-1);
}
}
static class ViewHolder {
TextView hourText, minuteText, am_pm, month, day;
}
}
이것들은 제가 전에 포스트 했던 것들과 많이 비슷하죠? 거의 똑같습니다.
이제 여기까지 오셨으면 다 끝났습니다. 다음은 실행 동영상입니다.
앱이 완성이 되었습니다. 그러나 이 앱은 부족한 점이 너무 많습니다.
1. 실시간 시간을 hoder에 지정을 하지 않아서 다시 로딩된다.
2. 리스트 삭제 시 마지막 리스트부터 삭제가 된다.
3. 새벽이 넘어가면 요일이 바뀌지 않는다.
4. 알람이 울리지 않는다.
5. 정렬이 되지 않는다.
등등 많은 문제점들과 부족한 점이 있습니다. 그러나 지금 이 포스트에선 간단한 레이아웃만을 다루려고 하는 거였지, 완전한 앱을 만드는 것이 목적이 아니었습니다. 나중에 제대로 된 알람 앱을 개발해 보도록 하겠습니다.
총 3편으로 이루어진 Adapter. 잘 보셨는지요? 혹시 아직 배우지 않고 이 글을 보신다면 제 포스트 한 번 참고하시면 좋을 것 같습니다. 감사합니다.