반응형

 

main 스레드와 새로만든 스레드 사이를 혼용해서 스레드를 사용하고자 할때 (UI 에 어떤 정보를 넘겨서 보여주고 싶을때 유용하다)

 

 

package com.jjjjj.myasynctask;

import androidx.appcompat.app.AppCompatActivity;

import android.os.AsyncTask;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.ProgressBar;

public class MainActivity extends AppCompatActivity {

    ProgressBar progressBar;

    int value;


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

        progressBar = findViewById(R.id.progressBar);

        Button button = findViewById(R.id.button);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                task.execute();     //BackGroundTask-AsyncTask 를 실행시킨다
            }
        });

        Button cancel = findViewById(R.id.button2);
        cancel.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                task.cancel(true);
            }
        });

    }

    class BackGroundTask extends AsyncTask<Integer, Integer, Integer>{

        //onPreExecute, onPostExecute, onProgressUpdate 전과 후에 UI 업데이트를 하고 싶을 때 사용 할 수 있다
        @Override
        protected void onPreExecute() {     //스레드 실행 전
            //super.onPreExecute();
            value = 0;
            progressBar.setProgress(value);

        }

        @Override
        protected void onPostExecute(Integer integer) { //스레드 실행 후(스레드 종료 후)
            //super.onPostExecute(integer);
            progressBar.setProgress(0);
        }

        @Override
        protected void onProgressUpdate(Integer... values) { // update, publishProgress(value); 이걸 통해서 이쪽으로 넘어온다
            //super.onProgressUpdate(values);
            progressBar.setProgress(values[0].intValue());
        }

        @Override
        protected Integer doInBackground(Integer... integers) { //스레드로 동작 하는 부분
            //return null;
            while(isCancelled() == false)
            {
                value += 1;

                if(value >= 100)
                {
                    break;
                }

                //onProgressUpdate 를 호출한다
                publishProgress(value);

                try{
                    Thread.sleep(1000);
                }
                catch (Exception e)
                {

                }
            }

            return value;       //onPostExecute 가 호출되게 된다
        }
    }

}

 

기본 화면

 

 

 

실행 버튼 누르면 progress bar 가 증가한다

 

취소 누르면 더이상 progress 바가 증가 하지 않는다

 

 

반응형
반응형
package com.jjjjj.mythread;

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

import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;


public class MainActivity extends AppCompatActivity {

    TextView textView;
    //MainHandler handler;

     Handler handler = new Handler();

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

        Button button = findViewById(R.id.button);
        textView = findViewById(R.id.textView);

        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                BackGroundThread thread = new BackGroundThread();
                thread.start();
            }
        });

        handler = new Handler();
    }

    //inner class
    //스레드는 Thread클래스를 상속 받고서 실행 가능하다
    class BackGroundThread extends Thread{
        int value =0;
        public void run()
        {
            for(int i=0;i<100;++i)
            {
                try {
                    Thread.sleep(1000);
                }
                catch (Exception e)
                {

                }

                value+=1;
                Log.d("my thread", "value : " + value);
                //textView.setText(value);      //여기선 스레드 간섭으로 크래쉬 남
                /*
                Message message = handler.obtainMessage();
                Bundle bundle = new Bundle();
                bundle.putInt("value", value);
                message.setData(bundle);

                handler.sendMessage(message);
                */


                //이렇게 하면 Runnable 이 main 스레드로 전달 되면서
                // 이 쓰레드가 메인 스레드에서 실행된다
                handler.post(new Runnable() {
                    @Override
                    public void run() {
                        textView.setText("" + value );
                    }
                });


            }
        }
    }

    //스레드 마다 핸들러를 만들수 있는데 이곳에 정의하면 main thread 에 handler 가 정의 된다
    /*
    class MainHandler extends Handler {

        //handleMessage  main thread 에서 동작한다
        @Override
        public void handleMessage(@NonNull Message msg) {
            super.handleMessage(msg);
            Bundle bundle = msg.getData();
            int value = bundle.getInt("value");
            textView.setText("" + value );

        }
    }
    */
}

 

다음 처럼 숫자가 증가하게 됩니다

반응형

'App' 카테고리의 다른 글

android : AsyncTask  (0) 2022.10.27
Android : Activity  (0) 2022.10.19
Java overriding methods when creating new instance of a class  (0) 2022.10.13
(View,ViewGroup) 안드로이드 View와 ViewGroup  (0) 2022.10.12
반응형

 

 

public class MainActivity extends AppCompatActivity {

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

        Button button = findViewById(R.id.button);

        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                //intent 시스템이 해석 할수 있는 정보다
                //Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse("tel:010-0000-0000"));
                //startActivity(intent);
/*
                Intent intent = new Intent();
                ComponentName name = new ComponentName("com.jjjjj.mycallIntent", "com.jjjjj.mycallIntent.MenuActivity" );

                intent.setComponent(name);
                startActivityForResult(intent, 101);
 */
            }
        });
    }
}

문자로 activity 띄우기

activity 간에는 Intent 로 데이터를 주고 받을수 있다

 

 

 

 

activity(액티비티, 서비스, 브로드캐스트 수신자) 에는 여러개가 있는데 각 구성요소들 사이에선 마찬가지로 Intent 를 통해서 데이터를 전달 할 수 있다

 

 

 

 

 

액티비티는 새로운 액티비티가 생성 되면 스택에쌓이는 구조로 생성 되게 된다

 

 

 

만약 동일한 액티비티를 또 띄우면 두번 중첩되어 쌓일 수 있는데 이때 FLAG_ACTIVITY_SINGLE_TOP 플래그를 통해서 하나의 액티비티만 띄울 수 잇께 할 수 있다 (하나 띄우고 동일한게 띄워질려고 할때 동일한 것을 재사용하게 한다)

 

 

코드

 

 

 

 

액티비티 수명 주기

 

 

반응형
반응형

This might be simple for seasoned java developers but I just cant seem to figure it out. I read a post from here. The code was

View v = new View(this) {
    @Override
    protected void onDraw(Canvas canvas) {
        System.out.println("large view on draw called");
        super.onDraw(canvas);
    }
};

It was an Android question. Here the user creates an instance of a view and overrides a method in a single line. Is there a name for this kind of coding?

My second doubt is, he overrides a protected method from another package. Isn't protected mean package private. I know this will work as I tried it out but I just couldn't figure out why it worked. So why is this code working?

I did try to google this and search in SO before asking but couldn't figure out an answer.

 

 

 

=>

That is not exactly a kind of coding. That is a Java anonymous class. It is very common in Android and in general with event listeners and that kind of stuff.

For more details you can read this link (probably not the best one):

The anonymous inner classes is very useful in some situation. For example consider a situation where you need to create the instance of an object without creating subclass of a class and also performing additional tasks such as method overloading.

About your second question, the keyword protected means that the method is only available to subclasses, so it is possible to override the method.

 


 

 

 

 

package com.jjjjj.myevent;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.view.MotionEvent;
import android.view.View;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {

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

        textView = findViewById(R.id.textView);
        View view = findViewById(R.id.view);
        view.setOnTouchListener(new View.OnTouchListener(){

            @Override
            public boolean onTouch(View var1, MotionEvent event)
            {
                int action = event.getAction();

                float curX = event.getX();
                float curY = event.getY();

                if(action ==  MotionEvent.ACTION_DOWN )
                {
                    println("손가락 눌림 : " + curX + ", " + curY);
                }
                else if(action ==  MotionEvent.ACTION_MOVE )
                {
                    println("손가락 움직임 : " + curX + ", " + curY);
                }
                else if(action ==  MotionEvent.ACTION_UP )
                {
                    println("손가락 뗌 : " + curX + ", " + curY);
                }
                return true;
            }

        });
    }


    public void println(String data)
    {
        textView.append(data + "\n");
    }
}

 

가장 상단 푸른색을 눌렀다가 움직이고 때면 하단의 글자 처럼 마우스에 대한 입력들을 볼 수가 있습니다

 

 

 

 


 

익명객체(익명클래스) 란?

이번시간에는 자바 익명객체(익명클래스)에 대해서 알아보도록 하겠습니다.

익명객체(익명클래스) 말그대로.. 이름이 없는 객체? 클래스?,,,그래서 무명클래스라고도 합니다.

언어의 사전적인 의미는 파악이 되셨을거라 생각됩니다.

그럼 도대체 이름도 없고 뭔가 근본도 없는것 같은 이 익명객체(익명클래스)를 왜 사용해야하는지.. 바로 설명들어가겠습니다.

 

 

익명객체(익명클래스) 를 사용하는 이유!!

이름 부터 눈치 채셨겠지만, 이름이 없다는 것은 별로 기억되지 않아도 된다는 거겠죠..

나중에 다시 불러질 이유가 없다는 뜻입니다. 이 말을 좀 더 있어보이게 말하면...

프로그램에서 일시적으로 한번만 사용되고 버려지는 객체입니다. 좀 더 풀어서 생각해보면 일시적으로 사용된다는 것은 나중에 재사용이 되지 않는다는 것이며, 재사용이 될 필요가 없다는 뜻은 확장성이 그렇게 좋지 못하다는 뜻입니다.

자바에서 확장성을 고려해서 코딩을하고 설계를 하는 것도 중요합니다. 정말 중요하죠.. 하지만, 최소한의 확장성이 성립되려면 어느정도 유지보수에 대한 이점이 있어야 하는데, 오히려 확장성을 고려해서 설계를 했다간 유지보수에서 더 불리한 경우가 있습니다(아래 예시 참고). 자,,,익명클래스를 사용해야하는 이유를 정리하자면....


1. 프로그램 내에서 일시적으로(단발성으로) 한번만 사용되어야 하는 객체일 경우
   -> UI 이벤트처리, 스레드 객체 등 (단발성 이벤트 처리)

2. 재사용성이 없고, 확장성을 활용하는 것이 유지보수에서 더 불리할 때

  -> 비즈니스 로직이 정말 재각각이며, 재사용성이 전혀없어 매번 클래스를 생성해야하는 비용이 더 많을때

 

 

 

익명객체(익명클래스) 구현하는 방법 (익명 자식객체 생성, 익명 구현객체 생성)

 

익명객체(클래스)를 구현하는 방법은 크게 2가지 입니다.

부모/자식간 상속아래 익명 자식객체를 생성할 것인가?

아니면, 인터페이스를 구현한 익명 구현객체를 생성할 것인가? (다음 시간에 다루겠음)

이번시간에는 명 자식객체에 대해서 알아보겠습니다.

 

익명 자식객체 생성 방법에는 아래 3가지방법으로 구현할 수 있습니다.
1. 필드의 초기값
2. 로컬변수의 초기값
3. 매개변수의 매개값

 

정의는 아래와 같이 하면 됩니다. (세미콜론 ; 으로 마무리)

 

부모클래스 [필드|변수] = new 부모클래스(매개값, ... ) {

 

};

 

자 그럼 코드를 보면서 설명하도록 하겠습니다.

package Anonymous;

public class Insect {

	void attack(){
		System.out.println("곤충은 공격을 한다");
	}
}

우선 곤충 Insect 이라는 부모클래스를 정의했습니다.

향후 Insect 부모 클래스는 일시적으로 정의된 익명객체를 담아둘 변수로 사용되기도 하고, 익명객체가 무엇을 재정의해야할지 어느정도 컨셉을 잡아주는 역할로 사용됩니다.(상속개념)

package Anonymous;

public class Anonymous {

	//★★방법 1 : 필드에 익명자식 객체를 생성 
	Insect spider1 = new Insect(){
		
		String name = "무당거미";
		//거미줄을 치다.
		void cobweb(){
			System.out.println("사각형으로 거미줄을 친다.");
		}
		
		@Override
		void attack() {
			System.out.println(name + " 독을 발사한다.");
		}
	};
	
	//★★방법2 : 로컬변수의 초기값으로 대입
	void method1(){
		Insect spider2 = new Insect(){
			
			String name = "늑대거미";
			//거미줄을 치다.
			void cobweb(){
				System.out.println("육각형으로 거미줄을 친다.");
			}
			
			@Override
			void attack() {
				System.out.println(name + " 앞니로 문다.");
			}
		};
		
		//로컬변수이기 때문에 메서드에서 바로 사용
		spider2.attack();
	}
	
	//★★방법3 : 익명객체 매개변수로 대입
	void method2(Insect spider){
		spider.attack();
	}
	
	
}

그다음, 부모(곤충)객체 필드에 자식 익명객체를 바로 정의하여 초기값을 할당하고 있습니다.

또, method1을 보시면 해당 메서드 내부에서 지역적으로 익명객체를 생성하고, 바로 spider2.attack() 메서드를 호출하여 익명객체를 사용했습니다.

 

마지막으로 부모(곤충)객체를 매개변수 인자로 받게끔 하고, 받은 후 바로 spider.attack() 메서드를 호출하여 사용하도록 했습니다. 이제 위에서 정의한 Anonymous 클래스를 아래 main 함수에서 호출하도록 하면 아래와 같이 결과값이 나옵니다.

 

 

 

 

 

참고로!! 익명자식객체에서 새롭게 정의된 필드와 메소드는 자식객체 레벨에서만 사용되기 때문에 외부에서는 사용할 수 없습니다! 생각해보면 익명객체를 받아준 변수는 부모타입의 클래스이기 때문에 부모레벨에서 정의된 필드나 메서드만 사용이 가능합니다.

그래서 a.spider1.name, a.spider1.cobweb() 은 사용할 수 없고

a.spider1.attack() 메서드는 사용할 수 있는 것입니다.

package Anonymous;

public class AnonymousExample {

	public static void main(String[] args) {
		
		Anonymous a = new Anonymous();
		
		//방법 1 : 익명객체 필드 사용
		a.spider1.attack();

		//방법2 : 익명객체 로컬 변수 사용
		a.method1();
		
		//방법3 : 매개변수로 익명개체 사용
		a.method2(new Insect(){
			String name = "타란툴라";
			
			//거미줄을 치다.
			void cobweb(){
				System.out.println("그냥 마구잡이로 친다.");
			}
			
			@Override
			void attack() {
				System.out.println(name + " 공격 안하고 후퇴한다..");
			};
		});
		
		//익명객체 내부에서 새롭게 정의된 필드,메서드는 부모객체로 생성된 spider1에서 접근할 수 없음!!!
		a.spider1.name = "왕거미"; //익명객체에서 새롭게 정의된 필드 (접근불가)
		a.spider1.cobweb(); //익명객체에서 새롭게 정의된 메서드 (접근불가)
		a.spider1.attack(); //부모클래스 Insect에서 오버라이딩해서 재정의한 메서드 (접근가능)
	}

}

결과

무당거미 독을 발사한다.
늑대거미 앞니로 문다.
타란툴라 공격 안하고 후퇴한다..

 

 

 

ref : https://stackoverflow.com/questions/7347612/java-overriding-methods-when-creating-new-instance-of-a-class

ref : https://limkydev.tistory.com/226

반응형

'App' 카테고리의 다른 글

android : AsyncTask  (0) 2022.10.27
android Handler 로 스레드 실행 post  (0) 2022.10.27
Android : Activity  (0) 2022.10.19
(View,ViewGroup) 안드로이드 View와 ViewGroup  (0) 2022.10.12
반응형

뷰(View)

 

뷰는 안드로이드 기본 화면을 구성하는 모든 기본 화면의 구성요소이다.

 

우리가 어플리케이션에서 보는 버튼, 테이블, id/pw 입력 칸 등등 모든 것이 뷰가 된다.

 

HTML로 예를 들어보면 이미지를 나타내는 <img src>도 하나의 뷰고 <div>, <p>, <a href> 모든 것이 뷰라고 할 수 있다.

 

 

 

위의 그림처럼 뷰는 뷰를 포함 할 수 있고, 중첩적으로 사용 할 수 있다.

 

이때 뷰 중에서 눈에 보이는 것들은 위젯이라 부르고, 눈에 보이지 않는 것들은 레이아웃이라고 부른다.

레이아웃은 그 안에 다른 뷰들을 담아둘 수 있는데 레이아웃도 뷰를 상속하여 정의되었기 때문에 레이아웃 안에 레이아웃도 담을 수 있습니다.

레이아웃 안에 레이아웃, 다시 그 레이아웃 안에 레이아웃을 넣는 방식을 사용하면 복잡하지만 다이나믹한 화면을 연출 할 수 있게 된다.

 

 



 

 

 

 

뷰 만들어 보기

 

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

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello world!"
        android:textSize="50dp" />
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello world!"
        android:textSize="50dp">
    </TextView>

</LinearLayout>

 

 

위와 같이 코드를 작성하면 우측 화면과 같게 나타난다.

 

위의 코드에서 보면 LinearLayout라는 것이 있다. 이것은 레이아웃인데 눈에 보이지 않는 뷰에 해당한다.

 

그리고 두개의 TextView가 있는데 이러한 것들을 위젯이라고 한다.

 

그리고 모든 뷰를 자세히 살펴보면 layout_width, layout_height를 가지고 있다.

 

즉, 뷰는 항상 다음과 같은 꼴을 가지게 된다.

 

 

<시작 태그
  속성1 = "속성값"
  속성2 = "속성값"
    ...
</끝 태그>

혹은

<시작 태그
  속성1 = "속성값"
  속성2 = "속성값"
    ...
/>

 

그리고 모든 뷰는 항상 layout_width, layout_height를 설정해줌으로써 실제 크기를 지정해주어야 한다.

 

이때 wrap_content는 해당 뷰가 가지는 크기에 딱 맞게 설정해주는 것이고,

match_parent(or fill_parent)는 현재 여백의 화면을 꽉 채울 수 있도록 하는 역할을 한다.

 

 

ref : http://weibeld.net/android/view-hierarchy.html

ref : https://ktko.tistory.com/entry/ViewViewGroup-%EC%95%88%EB%93%9C%EB%A1%9C%EC%9D%B4%EB%93%9C-View%EC%99%80-ViewGroup

반응형

'App' 카테고리의 다른 글

android : AsyncTask  (0) 2022.10.27
android Handler 로 스레드 실행 post  (0) 2022.10.27
Android : Activity  (0) 2022.10.19
Java overriding methods when creating new instance of a class  (0) 2022.10.13
반응형

 

 

이번 시간에는 StatelessWidget(이하 SLW)와 StatefulWidget(이하 SFW)의 대해서 알아보고 그 차이점은 무엇인지도 생각해본다.

 

 

SLW와 SFW 모두 UI를 가지는 화면을 구성할 때 사용하는 위젯 클래스다. 두 위젯 모두 Scaffold를 이용해 동일한 방식으로 화면을 구성하게 된다.

 

1. StatelessWidget

우선 화면을 하나 살펴보자.

 

위의 화면은 숫자 0을 출력하는 Text위젯과 두 개의 FloatingActionButton 버튼으로 구성되어 있다. 예상하겠지만 버튼을 누를 때마다 숫자가 1씩 증가하거나 감소하는 기능을 가지는 화면을 구성하고자 한 것이다.

이를 SLW 클래스로 구현하면 그 소스는 다음과 같다.

class SLWdemo extends StatelessWidget {

  int _count = 0;

  @override
  Widget build(BuildContext context) {
    print("** build - StatelessWidget Demo");
    return Scaffold(
      appBar: AppBar(title: Text("Stateless Widget")),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              "$_count",
              style: TextStyle(fontSize: 30),
            ),
            Row(
              mainAxisAlignment: MainAxisAlignment.center,
              children: <Widget>[
                FloatingActionButton(
                  heroTag: null,
                  child: Icon(Icons.add),
                  onPressed: () {
                    _count++;
                    print("value of _count = $_count");
                  },
                ),
                FloatingActionButton(
                  heroTag: null,
                  child: Icon(Icons.remove),
                  onPressed: () {
                    _count--;
                    print("value of _count = $_count");
                  },
                )
              ],
            )
          ],
        ),
      ),
    );
  }
}

코드를 살펴보면,

화면에 출력할 숫자 값을 저장할 _count 변수가 필드로 선언되어 있으며, Text 위젯에서 그 값을 출력하도록 하고 있다. 그리고 숫자를 가감할 각 FloatingActionButton의 onPressed 속성에서 _count의 값을 1씩 증가 혹은 감소시키고 있는 것을 확인할 수 있다. 추가로 값을 가감될 때마다 _count의 값을 print 함수로 출력하게 구현했다.

그럼 실제 버튼을 클릭할 때마다 화면상의 값이 변경될까?

그렇지 않다. 버튼을 클릭하여도 화면상의 숫자의 변화는 발생하지 않는다.

그러나 실행창을 확인하면 다음과 같이 실제 _count의 값은 정상적으로 변경되고 있는 것을 확인할 수 있다.

Performing hot reload...
Syncing files to device Android SDK built for x86...
I/flutter (23923): ** build - StatelessWidget Demo
Reloaded 0 of 468 libraries in 105ms.
I/flutter (23923): value of _count = 1
I/flutter (23923): value of _count = 2
I/flutter (23923): value of _count = 3
I/flutter (23923): value of _count = 2
I/flutter (23923): value of _count = 1

 

다시 말하면, 각 버튼을 클릭하여 해당 버튼의 onPressed 속성의 함수에 의해 _count의 값을 정상적으로 증가하거나 감소되고 있으나 변경된 _count 값이 Text 위젯에서 반영되고 있지 않다는 것을 확인할 수 있다.

이 점이 SLW의 특징이다. 이 특징을 이해하기 위해서는 build 메서드에 대해서 먼저 알아야 한다. build 메서드는 SLW과 SFW(더 정확히는 SFW의 State 클래스)에서 구현되며 화면을 구성할 UI들을 구현하는 메서드다.

 

 

 

즉 화면이 출력될 때 build 메서드가 호출되면서 build 메서드 내부에 구현한 UI 위젯들이 화면에 출력된다는 것이다.

 

 

 

이제 다시 SLW 코드를 살펴보자 build 메소드 내부 첫 번째 줄에 print 함수가 삽입되어 있는 것을 확인할 수 있으며 build 메서드가 호출될 때마다 실행창에 메시지를 출력할 것이다. 그리고 위 실행 창의 세 번째 줄에 메시지가 출력되는 것을 확인할 수 있다. 그런데 5번째 줄부터 내용을 다시 확인해 보면 버튼을 클릭할 때마다 _count 값을 출력하는 print 함수는 호출되고 있지만 build 메서드 첫 번째 줄의 print 함수는 호출되지 않고 있다.

이 점이 화면상에 숫자의 변화가 없는 이유이며 SLW의 특징이기도 하다.

StatelessWidget은 이름 그대로 상태(State)를 가지지 않는 위젯 클래스다. 그래서 SLW 내부의 모든 UI 위젯들은 상태를 가질 수 없으며 상태가 없으니 상태의 변화를 인지할 필요도 없고 할 수도 없는 것이다. 그래서 화면이 생성될 때 한 번만 build 메서드를 호출해서 화면을 구성한 후에는 build 함수가 다시 호출되지 않는다. 버튼을 클릭하여 _count의 값을 변경시키더라도 build 메서드는 호출되지 않으므로 화면 내 Text 위젯의 값도 변경되지 않는 것이다.

SLW을 정리하면,

SLW은 변화가 필요없는 화면을 구성할 때 사용하는 위젯 클래스이며, 그렇기 때문에 build 메서드는 한 번만 호출된다.

 

2. StatefullWidget

SFW은 한번 생성한 화면의 구성이 어떠한 이유로 인해 변경될 수 있는 경우에 사용하는 위젯 클래스다.

SFW의 경우도 다음과 같이 동일한 UI 구성의 화면으로 구현한다.

 

우선 SLW 데모에서 사용한 소스코드를 그대로 이용해서 SFW 데모 코드를 다음과 같이 작성한다.

class SFWdemo extends StatefulWidget {
  @override
  SFWdemoState createState() => SFWdemoState();
}

class SFWdemoState extends State<SFWdemo> {
  int _count = 0;

  @override
  Widget build(BuildContext context) {
    print("** build - StatefulWidget Demo");
    return Scaffold(
      appBar: AppBar(title: Text("Statefull Widget")),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              "$_count",
              style: TextStyle(fontSize: 30),
            ),
            Row(
              mainAxisAlignment: MainAxisAlignment.center,
              children: <Widget>[
                FloatingActionButton(
                  heroTag: null,
                  child: Icon(Icons.add),
                  onPressed: () {
                    _count++;
                    print("value of _count = $_count");
                  },
                ),
                FloatingActionButton(
                  heroTag: null,
                  child: Icon(Icons.remove),
                  onPressed: () {
                    _count--;
                    print("value of _count = $_count");
                  },
                )
              ],
            )
          ],
        ),
      ),
    );
  }
}

SFW으로 화면을 구성할 때에는 SLW의 경우와는 다르게 StatefulWidget을 상속하는 위젯 클래스와 State를 상속하는 상태 클래스 두 개로 구성된다. 그리고 화면을 구성하는 build 메서드를 SFW 클래스가 아닌 SFW 클래스 타입의 State를 상속하는 상태 클래스에서 구성한다.

왜 이렇게 언어를 개발했는지는 모르겠지만, 매번 하나의 화면을 두개의 클래스로 나눠서 개발하는 점이 번거롭다. 심지어 대부분의 경우 SFW 위젯 클래스는 상태 클래스를 생성시키는 기능만 하는 것이 다인 경우가 많다.

 

 

 

 

어쨌든 소스 코드로 돌아와서 상태 클래스 내부에 build 메서드의 내용은 위에서 살펴본 SLW 위젯의 소스코드와 일치한다.(AppBar의 텍스트 내용만 다르다.)

그런데 버튼을 클릭해보면 아직까진 화면상의 숫자의 변화는 발생하지 않고 실행 창의 프린트되는 값만 변경되는 것을 확인할 수 있다.

Performing hot reload...
Syncing files to device Android SDK built for x86...
I/flutter (23923): ** build - StatefulWidget Demo
Reloaded 0 of 468 libraries in 110ms.
I/flutter (23923): value of _count = 1
I/flutter (23923): value of _count = 2
I/flutter (23923): value of _count = 3
I/flutter (23923): value of _count = 2
I/flutter (23923): value of _count = 1

 

여기서 알 수 있는 것은, 단순히 StatefulWidget으로 구현한다고만 해서 화면내부 UI가 참조하는 값(속성)의 변화가 화면에 바로 반영되진 않는다는 것이다. 버튼을 클릭하여 _count의 값을 변경하여도 UI는 자신이 참조하는 _count의 값이 변경되었음을 인지하지 못하는다는 것이다. 실제로 실행창에 출력된 값을 보더라도 SLW의 경우와 같이 _count의 값은 변하지만 build 메서드는 호출되고 있지 않고 있는 것을 알 수 있다.

이렇게 변경된 값이 UI 위젯에 반영하기 위해 이용하는 메소드가 setState다.

소스코드를 아래와 같이 변경한다.

class SFWdemo extends StatefulWidget {
  @override
  SFWdemoState createState() => SFWdemoState();
}

class SFWdemoState extends State<SFWdemo> {
  int _count = 0;

  @override
  Widget build(BuildContext context) {
    print("** build - StatefulWidget Demo");
    return Scaffold(
      appBar: AppBar(title: Text("Statefull Widget")),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              "$_count",
              style: TextStyle(fontSize: 30),
            ),
            Row(
              mainAxisAlignment: MainAxisAlignment.center,
              children: <Widget>[
                FloatingActionButton(
                  heroTag: null,
                  child: Icon(Icons.add),
                  onPressed: () {
                    setState(() {
                      _count++;
                    });
                    print("value of _count = $_count");
                  },
                ),
                FloatingActionButton(
                  heroTag: null,
                  child: Icon(Icons.remove),
                  onPressed: () {
                    setState(() {
                      _count--;
                    });
                    print("value of _count = $_count");
                  },
                )
              ],
            )
          ],
        ),
      ),
    );
  }
}

 

위의 소스코드를 보면 기존의 코드의 각 버튼의 onPressed 속성에서 구현하는 함수의 내용이 조금 바뀌었다. _count 변수의 값을 setState 메서드로 감싼 후 변경하는 것을 알 수 있다.

메서드의 이름에서 알 수 있듯이 setState 메서드는 SFW 내부의 상태를 변경할 때 사용하는 메서드이며 setState 메서드에서 변경된 상태 값을 플랫폼에 전달하여 build 메서드가 호출되도록 한다.

좀 더 자세히 설명하자면,

위 코드에서는 필드인 _count 변수가 이 화면구성의 상태(State)가 된다. 그리고 이 필드는 Text 위젯에서 참조하고 있다. 각 버튼이 클릭될 때 setState 메서드 내부에서 상태 값인 _count의 값을 변경하면, 변경과 동시에 변경 사실이 플랫폼으로 전달되어 build 메서드가 다시 호출되게 되고 Text 위젯은 참조하고 있는 _count의 최신 값을 화면에 출력하게 되는 것이다.

화면을 보면 다음과 같이 클릭하는 버튼에 따라 값이 변경되는 것을 확인할 수 있다.

 

실행창도 확인해 보자.

Performing hot reload...
Syncing files to device Android SDK built for x86...
I/flutter (23923): ** build - StatefulWidget Demo
Reloaded 0 of 468 libraries in 109ms.
I/flutter (23923): value of _count = 1
I/flutter (23923): ** build - StatefulWidget Demo
I/flutter (23923): value of _count = 2
I/flutter (23923): ** build - StatefulWidget Demo
I/flutter (23923): value of _count = 3
I/flutter (23923): ** build - StatefulWidget Demo
I/flutter (23923): value of _count = 2
I/flutter (23923): ** build - StatefulWidget Demo
I/flutter (23923): value of _count = 1
I/flutter (23923): ** build - StatefulWidget Demo

버튼이 클릭될 때마다 _count의 값이 변경되고 있으며 동시에 build 메서드가 호출되는 것을 확인할 수 있다.

 

SFW은 다음과 같이 정리할 수 있다.

  • SFW은 화면의 구성이 상태 변화에 따라 재구성되어야 할 때 사용된다.
  • SFW의 상태 변경은 setState 메서드를 이용해서 변경해야 한다.
  • 플랫폼은 setState 메서드가 호출될 때마다 build 메서드를 재호출하여 화면을 다시 그린다.

 

3. 전체 소스 코드

import 'package:flutter/material.dart';

void main() {
  runApp(MaterialApp(
    home: MainPage(),
  ));
}

class MainPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text("MainPage")),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            RaisedButton(
              child: Text("Launch StatelessWidget"),
              onPressed: () {
                Navigator.push(context,
                    MaterialPageRoute(builder: (context) => SLWdemo()));
              },
            ),
            RaisedButton(
              child: Text("Launch StatefullWidget"),
              onPressed: () {
                Navigator.push(context,
                    MaterialPageRoute(builder: (context) => SFWdemo()));
              },
            )
          ]
              .map((children) => Container(
                    width: 200,
                    child: children,
                  ))
              .toList(),
        ),
      ),
    );
  }
}

class SLWdemo extends StatelessWidget {
  int _count = 0;

  @override
  Widget build(BuildContext context) {
    print("** build - StatelessWidget Demo");
    return Scaffold(
      appBar: AppBar(title: Text("Stateless Widget")),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              "$_count",
              style: TextStyle(fontSize: 30),
            ),
            Row(
              mainAxisAlignment: MainAxisAlignment.center,
              children: <Widget>[
                FloatingActionButton(
                  heroTag: null,
                  child: Icon(Icons.add),
                  onPressed: () {
                    _count++;
                    print("value of _count = $_count");
                  },
                ),
                FloatingActionButton(
                  heroTag: null,
                  child: Icon(Icons.remove),
                  onPressed: () {
                    _count--;
                    print("value of _count = $_count");
                  },
                )
              ],
            )
          ],
        ),
      ),
    );
  }
}

class SFWdemo extends StatefulWidget {
  @override
  SFWdemoState createState() => SFWdemoState();
}

class SFWdemoState extends State<SFWdemo> {
  int _count = 0;

  @override
  Widget build(BuildContext context) {
    print("** build - StatefulWidget Demo");
    return Scaffold(
      appBar: AppBar(title: Text("Statefull Widget")),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              "$_count",
              style: TextStyle(fontSize: 30),
            ),
            Row(
              mainAxisAlignment: MainAxisAlignment.center,
              children: <Widget>[
                FloatingActionButton(
                  heroTag: null,
                  child: Icon(Icons.add),
                  onPressed: () {
                    setState(() {
                      _count++;
                    });
                    print("value of _count = $_count");
                  },
                ),
                FloatingActionButton(
                  heroTag: null,
                  child: Icon(Icons.remove),
                  onPressed: () {
                    setState(() {
                      _count--;
                    });
                    print("value of _count = $_count");
                  },
                )
              ],
            )
          ],
        ),
      ),
    );
  }
}
 
 
반응형

+ Recent posts