[Android] BaseAdapter를 활용한 알람 앱 간단한 레이아웃 구성하기

Android/실습 · 2020. 6. 11. 14:09
반응형

 

 이번 포스트에서 간단하게 알람 앱을 구현해 보겠습니다. 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는 시간을 설정할 수 있는 아주 좋은 아이템입니다. 나중에 따로 한번 다뤄보겠습니다.

 

 다음은 커스텀 레이아웃을 만들 차례입니다. 앞선 포스트에서 만드는 방법을 올려두었으니 참고해 주시기 바랍니다.

 

https://50billion-dollars.tistory.com/entry/Android-%EC%95%88%EB%93%9C%EB%A1%9C%EC%9D%B4%EB%93%9C-%EB%A6%AC%EC%8A%A4%ED%8A%B8%EB%B7%B0-%EB%AA%A8%EC%84%9C%EB%A6%AC-%EB%91%A5%EA%B8%80%EA%B2%8C-%EB%A7%8C%EB%93%A4%EA%B8%B0

 

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

 

 스위치에 딱 이 한 줄 코드만 설정해 놓으시면 됩니다. 자바는 자바 소스에서 스위치 생성 시 주로 사용하는 방법입니다.

 

 이것에 대해 더욱 자세히 알고 싶은 분은 제가 참고한 블로그를 올려둘 테니 참고하시기 바랍니다.

 

https://happydev.kr/36

 

커스텀 리스트뷰에서 아이템 클릭(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. 잘 보셨는지요? 혹시 아직 배우지 않고 이 글을 보신다면 제 포스트 한 번 참고하시면 좋을 것 같습니다. 감사합니다.

반응형