본문 바로가기

안드로이드

딥러닝 -두더지 잡기 0223

1.Fragment1.java

package com.psy.app0223;

import android.os.Bundle;

import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;

import android.os.Handler;
import android.os.Message;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;

import java.util.Random;

public class Fragment1 extends Fragment {

    ImageView[] dodos = new ImageView[9];
    DoThread[] thread = new DoThread[9];

    TextView tv_time;
    TextView tv_cnt;
    int cnt;

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_1,container,false);

        tv_time = view.findViewById(R.id.tv_time);
        tv_cnt = view.findViewById(R.id.tv_cnt);

        // 두더지(ImageView) 9개 findViewByid
        for(int i =0; i<dodos.length;i++){
            int imgID = getResources().getIdentifier("imageView"+(i+1),"id",getContext().getPackageName());
            dodos[i] = view.findViewById(imgID);
            dodos[i].setTag("0");
            thread[i] = new DoThread(dodos[i]);


            thread[i].start();
            TimeThread thread = new TimeThread(tv_cnt);
            thread.start();





            dodos[i].setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    if (view.getTag().toString().equals("1")){
                        Toast.makeText(getContext(), "잡았다!", Toast.LENGTH_SHORT).show();
                        view.setTag("0");
                        cnt++;
                        tv_time.setText(cnt+"");
                    }else {
                        Toast.makeText(getContext(), "못 잡았다!", Toast.LENGTH_SHORT).show();
                        cnt--;
                        tv_time.setText(cnt+"");
                    }




                }
            });

        }

        return view;
    }

    Handler handler2 = new Handler(){
        @Override
        public void handleMessage(@NonNull Message msg) {
            super.handleMessage(msg);
            TextView obj = (TextView) msg.obj;
            int i =msg.arg1;

            obj.setText(i+"");


        }
    };
    Handler handler = new Handler(){
        @Override
        public void handleMessage(@NonNull Message msg) {
            super.handleMessage(msg);

            ImageView img = (ImageView) msg.obj;
            img.setImageResource(msg.arg1);
            img.setTag(msg.arg2+"");
        }
    };

    class DoThread extends Thread{
        private ImageView dodo; // 이 쓰레드 담당 두더지

        public DoThread(ImageView dodo){
            this.dodo = dodo;
        }

        @Override
        public void run() {

            while (true){
                // 랜덤 시간만큼 내려간 상태 유지
                int offTime = new Random().nextInt(5000)+500;//0.5~5.5 초
                try {
                    Thread.sleep(offTime);

                    Message msg = new Message();
                    msg.obj = dodo;
                    msg.arg1 = R.drawable.on;
                    msg.arg2 = 1;
                    // 핸들러한테 보내줄거임
                    handler.sendMessage(msg);
                    int onTime = new Random().nextInt(1000) +500; //0.5~1.5 초
                    Thread.sleep(onTime);
                    // 한번 보낸 Message 객체는 재활용 X
                    Message msg2 = new Message();
                    msg2.obj = dodo;
                    msg2.arg1 = R.drawable.off;
                    msg2.arg2 =0;
                    handler.sendMessage(msg2);

                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }

            }

        }
    }

    class TimeThread extends Thread{

        // Thread 메소드 실행 순서 (Life Cycle)
        // start -> run -> destroy

        private TextView tv;

        public  TimeThread(TextView tv){
            this.tv = tv;
        }
        @Override
        public void run() {

            for(int i = 30; i >= 0 ; i--){

                Message msg = new Message();

                msg.obj = tv;
                msg.arg1 = i;

                handler2.sendMessage(msg);//위에서 구성한MSg 객체 보내기~


                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }

        }
    }

}


고칠점

 

 

변수명

  • dodos와 같은 변수명은 가독성이 떨어지므로, 보통 객체나 변수의 의미가 잘 드러나는 이름을 사용합니다.
  • tv_time 대신에 더 명확한 tv_cnt로 이름을 변경하는 것이 좋을 것 같습니다.

중복코드

  • handler와 handler2의 코드가 거의 유사합니다. 메소드로 분리하면 중복 코드를 제거할 수 있을 것 같습니다.
  • 또한, DoThread와 TimeThread 클래스 안에서도 비슷한 구문을 반복하고 있습니다. 이 역시 중복 코드를 제거하면 좋습니다.

코드레이아웃

  • 클래스 안에서 정의된 메소드들이 나열되어 있으므로, 먼저 DoThread와 TimeThread를 나누고, 각각의 변수와 메소드를 정의하는 것이 가독성이 좋습니다.

예외처리

  • DoThread 클래스에서 예외 처리를 위해 RuntimeException을 던지고 있습니다. 이는 예외 발생 시 스레드를 종료하고 앱이 비정상적으로 종료될 수 있습니다. 좀 더 적절한 예외 처리 방법을 사용해야합니다  

스레드 안전성

  • 현재 코드에서는 여러 스레드가 공유하는 변수인 cnt를 동기화하지 않았습니다. 이를 동기화하여 스레드 안전성을 확보해야합니다

코드 가독성

  • DoThread 클래스 안에서 다수의 중첩된 try-catch문이 존재하고 있습니다. 이를 보다 가독성 좋게 수정해야됩니다

주석

  • 코드 내에 메소드와 변수, 클래스의 역할을 설명하는 주석이 없습니다. 간단한 주석을 추가하여 코드를 보다 쉽게 이해할 수 있도록 해야됩니다

불필요한 코드

  1. DoThread 클래스 안에서 이미 while (true)로 무한 루프를 돌고 있는데, try-catch문 안에서 while문을 한 번 더 반복하고 있습니다. 이를 제거하여 코드를 간결하게 만들어야됩니다

2. fragment_1.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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:id="@+id/frameLayout2"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#EAECB2"
    tools:context=".Fragment1">

    <!-- TODO: Update blank fragment layout -->

    <TextView
        android:id="@+id/tv_time"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="110dp"
        android:text="0"
        android:textSize="34sp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <TableLayout
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_marginTop="30dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/tv_time">

        <TableRow
            android:layout_width="match_parent"
            android:layout_height="match_parent">

            <ImageView
                android:id="@+id/imageView1"
                android:layout_width="wrap_content"
                android:layout_height="130dp"
                android:layout_weight="1"
                android:src="@drawable/off" />

            <ImageView
                android:id="@+id/imageView2"
                android:layout_width="wrap_content"
                android:layout_height="130dp"
                android:layout_weight="1"
                android:src="@drawable/off" />

            <ImageView
                android:id="@+id/imageView3"
                android:layout_width="wrap_content"
                android:layout_height="130dp"
                android:layout_weight="1"
                android:src="@drawable/off" />
        </TableRow>

        <TableRow
            android:layout_width="match_parent"
            android:layout_height="match_parent">

            <ImageView
                android:id="@+id/imageView4"
                android:layout_width="wrap_content"
                android:layout_height="130dp"
                android:layout_weight="1"
                android:src="@drawable/off" />

            <ImageView
                android:id="@+id/imageView5"
                android:layout_width="wrap_content"
                android:layout_height="130dp"
                android:layout_weight="1"
                android:src="@drawable/off" />

            <ImageView
                android:id="@+id/imageView6"
                android:layout_width="wrap_content"
                android:layout_height="130dp"
                android:layout_weight="1"
                android:src="@drawable/off" />
        </TableRow>

        <TableRow
            android:layout_width="match_parent"
            android:layout_height="match_parent">

            <ImageView
                android:id="@+id/imageView7"
                android:layout_width="wrap_content"
                android:layout_height="130dp"
                android:layout_weight="1"
                android:src="@drawable/off" />

            <ImageView
                android:id="@+id/imageView8"
                android:layout_width="wrap_content"
                android:layout_height="130dp"
                android:layout_weight="1"
                android:src="@drawable/off" />

            <ImageView
                android:id="@+id/imageView9"
                android:layout_width="wrap_content"
                android:layout_height="130dp"
                android:layout_weight="1"
                android:src="@drawable/off" />
        </TableRow>

    </TableLayout>

    <TextView
        android:id="@+id/tv_cnt"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="16dp"
        android:layout_marginEnd="16dp"
        android:text="30"
        android:textSize="34sp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>
  1. TableLayout의 android:layout_height를 0dp로 설정
    TableLayout는 세로 방향으로만 늘어날 수 있도록 layout_height를 0dp로 설정하고, 가로 방향으로 늘어나게 하기 위해 layout_weight를 사용합니다. 따라서 TableLayout의 android:layout_height를 0dp로 설정해야 합니다.

  2. TextView의 id를 의미있게 변경
    tv_time과 tv_cnt는 각각 시간과 카운트를 나타내는데, 이러한 정보를 명확하게 전달하기 위해서는 더 의미있는 id를 사용해주는 것이 좋습니다. 예를 들어 tv_time을 tv_timer로, tv_cnt를 tv_count로 변경해주는 것이 좋습니다.

  3. Thread를 실행하기 전에 TimeThread의 인스턴스를 생성
    TimeThread는 타이머를 구현하는데 사용되는 Thread입니다. 현재 코드에서는 DoThread의 인스턴스를 생성하기 전에 TimeThread의 인스턴스를 생성하고 실행시키고 있습니다.
    이 경우, DoThread가 실행되기 전에 이미 TimeThread가 실행되고, 타이머가 이미 시작된 상태가 됩니다.
    따라서 DoThread의 인스턴스를 생성하기 전에 TimeThread의 인스턴스를 생성해주는 것이 좋습니다.

3.Fragment2.java

package com.psy.app0223;

import android.os.Bundle;

import androidx.fragment.app.Fragment;

import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

/**
 * A simple {@link Fragment} subclass.
 * Use the {@link Fragment2#newInstance} factory method to
 * create an instance of this fragment.
 */
public class Fragment2 extends Fragment {

    // TODO: Rename parameter arguments, choose names that match
    // the fragment initialization parameters, e.g. ARG_ITEM_NUMBER
    private static final String ARG_PARAM1 = "param1";
    private static final String ARG_PARAM2 = "param2";

    // TODO: Rename and change types of parameters
    private String mParam1;
    private String mParam2;

    public Fragment2() {
        // Required empty public constructor
    }

    /**
     * Use this factory method to create a new instance of
     * this fragment using the provided parameters.
     *
     * @param param1 Parameter 1.
     * @param param2 Parameter 2.
     * @return A new instance of fragment Fragment2.
     */
    // TODO: Rename and change types and number of parameters
    public static Fragment2 newInstance(String param1, String param2) {
        Fragment2 fragment = new Fragment2();
        Bundle args = new Bundle();
        args.putString(ARG_PARAM1, param1);
        args.putString(ARG_PARAM2, param2);
        fragment.setArguments(args);
        return fragment;
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        if (getArguments() != null) {
            mParam1 = getArguments().getString(ARG_PARAM1);
            mParam2 = getArguments().getString(ARG_PARAM2);
        }
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        // Inflate the layout for this fragment
        return inflater.inflate(R.layout.fragment_2, container, false);
    }
}

해당 코드는 Fragment2 클래스를 정의하고 onCreateView() 메소드를 구현하는 안드로이드 프래그먼트 코드입니다.

 

Fragment2 클래스는 매개변수를 받지 않는 기본 생성자와, newInstance() 메소드로 매개변수를 전달받아 Bundle 객체에 저장하고 Fragment2 인스턴스에 전달하는 생성자를 가지고 있습니다.

 

onCreate() 메소드는 Fragment2 인스턴스가 생성될 때 호출되며, getArguments() 메소드를 통해 전달된 인수들을 가져와 멤버 변수에 저장합니다.

 

onCreateView() 메소드는 프래그먼트가 화면에 표시될 때 호출되어 프래그먼트 레이아웃을 인플레이트하여 반환합니다. 이 코드에서는 R.layout.fragment_2를 인플레이트하여 반환합니다.

 

코드 자체는 문제가 없어보이지만, 코드에서 어떤 기능을 하는지 파악하기 어렵습니다. 따라서 코드의 목적이나 기능이 무엇인지에 대한 정보가 추가되면 더욱 도움이 될 것입니다.

 

4. fragment_2.xml

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#6AFF5F"
    tools:context=".Fragment2">

    <!-- TODO: Update blank fragment layout -->
    <TextView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:text="여기는 2번쨰"
        android:textSize="34sp" />

</FrameLayout>

이 코드는 안드로이드 앱에서 Fragment2를 보여주는 레이아웃 파일입니다.
FrameLayout을 사용하고, 배경색을 설정하고, 하나의 TextView를 가지고 있습니다.

TextView에는 "여기는 2번째"라는 텍스트와 34sp의 글꼴 크기가 설정되어 있습니다.

Fragment2를 보여주기 위한 최소한의 레이아웃 구성이며,

Fragment1과 같이 구성하는 경우 두 Fragment 모두 레이아웃이 보이게 됩니다.

다만, 앱에서 필요한 기능에 따라서 내용물을 추가하고 레이아웃을 변경해야 할 수 있습니다.

 

5. fragment3.java

package com.psy.app0223;

import android.os.Bundle;

import androidx.fragment.app.Fragment;

import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

/**
 * A simple {@link Fragment} subclass.
 * Use the {@link Fragment3#newInstance} factory method to
 * create an instance of this fragment.
 */
public class Fragment3 extends Fragment {

    // TODO: Rename parameter arguments, choose names that match
    // the fragment initialization parameters, e.g. ARG_ITEM_NUMBER
    private static final String ARG_PARAM1 = "param1";
    private static final String ARG_PARAM2 = "param2";

    // TODO: Rename and change types of parameters
    private String mParam1;
    private String mParam2;

    public Fragment3() {
        // Required empty public constructor
    }

    /**
     * Use this factory method to create a new instance of
     * this fragment using the provided parameters.
     *
     * @param param1 Parameter 1.
     * @param param2 Parameter 2.
     * @return A new instance of fragment Fragment3.
     */
    // TODO: Rename and change types and number of parameters
    public static Fragment3 newInstance(String param1, String param2) {
        Fragment3 fragment = new Fragment3();
        Bundle args = new Bundle();
        args.putString(ARG_PARAM1, param1);
        args.putString(ARG_PARAM2, param2);
        fragment.setArguments(args);
        return fragment;
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        if (getArguments() != null) {
            mParam1 = getArguments().getString(ARG_PARAM1);
            mParam2 = getArguments().getString(ARG_PARAM2);
        }
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        // Inflate the layout for this fragment
        return inflater.inflate(R.layout.fragment_3, container, false);
    }
}

이 코드는 안드로이드의 Fragment를 사용하여 화면을 구성하는 코드입니다.

Fragment3 클래스는 빈 화면을 나타내기 위한 코드이며,

onCreateView() 메소드에서 레이아웃을 inflate 하여 화면에 보여주고 있습니다.

 

6. fragment_3.xml

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#EF7474"
    tools:context=".Fragment3">

    <!-- TODO: Update blank fragment layout -->
    <TextView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:text="여기는 세번째"
        android:textSize="34sp" />

</FrameLayout>

이 코드들은 안드로이드의 Fragment를 이용하여 여러 개의 화면을 전환하는 앱을 만들기 위한 코드입니다.

 

각각의 코드를 봤을 때, Fragment1 클래스에서는 두더지 게임을 구현하는데 필요한 코드들이 들어가 있고,

Fragment2 클래스와 Fragment3 클래스는 단순히 텍스트뷰를 띄우는 코드들이 들어가 있습니다.

코드의 가독성이 좋고 불필요한 코드들이 없어서 깔끔해 보입니다.

하지만 코드들이 다른 부분들과 너무 다른 이름을 가지고 있는데

이는 일관성을 유지하기 위해서라면 개발자는 일관성 있는 이름을 사용하는 것이 좋습니다.

7. fragment4.java

package com.psy.app0223;

import android.os.Bundle;

import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;

import android.os.Handler;
import android.os.Message;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.TextView;


public class Fragment4 extends Fragment {
    Button btn_1 , btn_2;
    TextView tv_1, tv_2;


    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        // java 랑 짝지어진 xml 파일을 view객체(Inflate해서)로 만들어서 리턴~
        View view = inflater.inflate(R.layout.fragment_4, container, false);

        tv_1 = view.findViewById(R.id.tv_1);
        tv_2 = view.findViewById(R.id.tv_2);
        btn_1 = view.findViewById(R.id.btn_1);
        btn_2 = view.findViewById(R.id.btn_2);

        // 1. 1~30까지 숫자를 세느 Thread를 설계 (클래스로) - 설계는 1개
        // 2. 버튼 눌렀을 때 Thread 객체를 생성하고 start! - 생성은 2개
        btn_1.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // Thread 객체생성
                TimeThread thread = new TimeThread(tv_1);
                thread.start();
            }
        });
        btn_2.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // Thread 객체생성
                TimeThread thread = new TimeThread(tv_2);
                thread.start();
            }
        });

        return view;
    }

        Handler handler = new Handler(){
            // handler에 메세지 보낼대는 sendmessage
            // 받은 메세지 처리할 때는 handlerMessage 에서 !

            @Override
            public void handleMessage(@NonNull Message msg) {
                super.handleMessage(msg);

                // 매개 변수 Message 객체는 Thread 에서 보내준 메세지 그대로~
                // 받아온 Message 객체에서 textView랑 꺼내서 setText 해보기~

                TextView obj = (TextView) msg.obj;
                int i =msg.arg1;

                obj.setText(i+"");

            }
        };


    class TimeThread extends Thread{

        // Thread 메소드 실행 순서 (Life Cycle)
        // start -> run -> destroy

        private TextView tv;

        public  TimeThread(TextView tv){
            this.tv = tv;
        }
        @Override
        public void run() {

            for(int i = 1; i <= 30 ; i++){
                //tv.setText(i+"");
                // 개발자가 설계한 Thread 에서는 UI 작업(setText, setImageResource 등등) 못함
                // Handler에게 요청(Message를 보낸다) 해서 MainThread로 작업을 전달
                Message msg = new Message();
                // 우리 에전에 배웟던 ViewHoler 랑 비슷, 데이터를 담는 역할만 함!
                // Object 타입 객체 1개, int 타입 변수 2개(arg1, arg2)
                // 그렇다면! 지금 이 예제에서 handler한테 보내줘야할 데이터는 ?
                // - 숫자를 적어야하는 TextView, 적힐 숫자(i)
                msg.obj = tv;
                msg.arg1 = i;

                handler.sendMessage(msg);//위에서 구성한MSg 객체 보내기~


                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }

        }
    }

}

8. fragment_4.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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:id="@+id/frameLayout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#FFFFFF"
    tools:context=".Fragment4" >

    <TextView
        android:id="@+id/tv_1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginBottom="450dp"
        android:text="0"
        android:textSize="50sp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <Button
        android:id="@+id/btn_1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginBottom="350dp"
        android:text="1번 COUNT 시작!"
        android:textSize="20sp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/tv_1" />

    <TextView
        android:id="@+id/tv_2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginBottom="200dp"
        android:text="0"
        android:textSize="50sp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/btn_1" />

    <Button
        android:id="@+id/btn_2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginBottom="50dp"
        android:text="1번 COUNT 시작!"
        android:textSize="20sp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/tv_2" />
</androidx.constraintlayout.widget.ConstraintLayout>

먼저 Fragment4.java 파일입니다.

  1. onCreateView() 메소드에서 view 객체를 반환합니다.
  2. btn_1과 btn_2를 클릭했을 때 TimeThread 객체를 생성하고 start()를 호출합니다.
  3. TimeThread 클래스는 Thread 클래스를 상속합니다.
  4. TimeThread 클래스에서 run() 메소드를 구현하여 1초마다 handler 객체에 메시지를 보내고, TextView에 메시지의 arg1 값을 표시합니다.

고칠점

  1. handler 객체를 생성했지만 sendMessage() 메소드를 사용하는 부분이 없습니다.
    따라서 run() 메소드가 실행되어도 UI 스레드에게 메시지가 전달되지 않습니다.
    이 문제를 해결하려면, onCreateView() 메소드에서 handler 객체를 생성하고,
    TimeThread 클래스에서 handler 객체를 직접 참조하도록 코드를 수정해야 합니다.

  2. Thread.sleep(1000) 대신 Thread.sleep(TimeUnit.SECONDS.toMillis(1))를 사용하는 것이 더 안전합니다.

그리고 fragment_4.xml 파일은 큰 문제가 없어 보입니다.

  1. TextView와 Button 위젯들을 배치하여 2개의 카운트 다운이 가능하도록 구성하였습니다.

9.MainActivity.java

package com.psy.app0223;

import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.view.MenuItem;
import android.widget.FrameLayout;

import com.google.android.material.bottomnavigation.BottomNavigationView;
import com.google.android.material.navigation.NavigationBarView;

public class MainActivity extends AppCompatActivity {

    FrameLayout layout;
    BottomNavigationView bnv;

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

        layout = findViewById(R.id.Framelayout);
        bnv = findViewById(R.id.bottomNavigationView);

        // bnv 를 클릭했을 때 어떤 메뉴가 선택됬는지 검사해서 frameLayout 에 들어갈
        // frameLayout 에 들어갈 fragment 갈아 끼워주면 됨

        getSupportFragmentManager().beginTransaction()
                .replace(R.id.Framelayout, new Fragment1()).commit();

        bnv.setOnItemSelectedListener(new NavigationBarView.OnItemSelectedListener() {


            @Override
            public boolean onNavigationItemSelected(@NonNull MenuItem item) {

                switch(item.getItemId()){
                    case R.id.item1:
                        getSupportFragmentManager().beginTransaction()
                                .replace(R.id.Framelayout, new Fragment1()).commit();
                        break;
                    case R.id.item2:
                        getSupportFragmentManager().beginTransaction()
                                .replace(R.id.Framelayout, new Fragment2()).commit();
                        break;
                    case R.id.item3:
                        getSupportFragmentManager().beginTransaction()
                                .replace(R.id.Framelayout, new Fragment3()).commit();
                        break;
                    case R.id.item4:
                        getSupportFragmentManager().beginTransaction()
                                .replace(R.id.Framelayout, new Fragment4()).commit();
                        break;
                }
                // 현재 이벤트가 종료되었는지 여부를 리턴 => longClick!! 할 때
                return true;
            }
        });


    }
}

위 코드는 안드로이드 앱에서 BottomNavigationView를 사용하여 프래그먼트를 전환하는 기능을 구현한 코드입니다.

 

  • onCreate() 메서드에서는 setContentView()로 화면을 구성하고, findViewById()로 레이아웃 내부에 존재하는 뷰들의 참조를 가져옵니다.
  • getSupportFragmentManager().beginTransaction()으로 FragmentTransaction 객체를 가져온 후, replace() 메서드로 프래그먼트를 교체합니다. 프래그먼트는 new 키워드로 생성하여 넘겨줍니다.
  • setOnItemSelectedListener() 메서드를 이용하여 BottomNavigationView의 아이템 선택 이벤트를 처리합니다. onNavigationItemSelected() 콜백 메서드에서 선택한 아이템에 따라 프래그먼트를 교체합니다.

고칠점 

메모리 누수 등의 문제를 방지하기 위해 프래그먼트를 사용할 때는 addToBackStack() 메서드를 이용하여 백스택에 추가해 주는 것이 좋습니다.

 

10.activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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">

    <androidx.constraintlayout.widget.Guideline
        android:id="@+id/guideline"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        app:layout_constraintGuide_percent="0.9" />

    <com.google.android.material.bottomnavigation.BottomNavigationView
        android:id="@+id/bottomNavigationView"
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:itemIconTint="@color/bottomcolor"
        app:itemTextColor="@color/bottomcolor"
        app:labelVisibilityMode="labeled"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="@+id/guideline"
        app:menu="@menu/menuitem" />

    <FrameLayout
        android:id="@+id/Framelayout"
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:layout_constraintBottom_toTopOf="@+id/guideline"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent">

    </FrameLayout>

</androidx.constraintlayout.widget.ConstraintLayout>

코드를 확인해보니 깔끔하게 잘 작성된 것 같습니다! 몇 가지 포인트를 더 보완하면 좋겠습니다.

  1. XML 레이아웃 파일에서 Guideline 사용

Guideline을 이용하면, 레이아웃을 배치할 때 좀 더 유연하게 배치할 수 있습니다. 하지만 현재의 XML 코드에서는 Guideline을 사용하면 좋을 것 같습니다. Guideline을 사용하면, BottomNavigationView의 바로 위쪽 레이아웃을 쉽게 정렬할 수 있습니다.

  1. layout_width, layout_height 속성을 직접 지정하지 않기

FrameLayout이나 BottomNavigationView의 layout_width와 layout_height 속성을 직접 지정하지 않고, match_parent와 0dp와 같은 match_constraint와 같은 값으로 설정하면, 더 깔끔한 레이아웃 코드를 작성할 수 있습니다.

  1. Fragment에 대한 명시적인 타입 지정

Fragment1, Fragment2, Fragment3, Fragment4를 생성할 때, 클래스를 명시적으로 지정하는 것이 좋습니다. 

 

  1. Activity에서 Fragment를 생성하는 코드에 대한 리팩토링

마지막으로, Activity에서 Fragment를 생성하는 코드를 리팩토링할 수 있습니다. 아래와 같이 코드를 작성하면, 코드가 깔끔하게 보입니다.

 

파일 다운 링크 :https://github.com/yoonhyochang/Android