1. SampleFragment
1-1. fragment_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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:orientation="vertical"
tools:context=".MainFragment" >
<TextView
android:id="@+id/textView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="메인 프래그먼트"
android:textSize="30sp" />
<Button
android:id="@+id/button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="메뉴 화면으로" />
</LinearLayout>
1-2. fragment_menu.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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="@android:color/holo_orange_light"
android:orientation="vertical"
tools:context=".MainFragment">
<TextView
android:id="@+id/textView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="메뉴 프래그먼트"
android:textSize="30sp" />
<Button
android:id="@+id/button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="메인 화면으로" />
</LinearLayout>
1-3. MainActivity.java
public class MainActivity extends AppCompatActivity {
MainFragment mainFragment;
MenuFragment menuFragment;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mainFragment = (MainFragment) getSupportFragmentManager().findFragmentById(R.id.mainFragment);
menuFragment = new MenuFragment();
}
public void onFragmentChanged(int index) {
if (index == 0) {
getSupportFragmentManager().beginTransaction().replace(R.id.container, menuFragment).commit();
} else if (index == 1) {
getSupportFragmentManager().beginTransaction().replace(R.id.container, mainFragment).commit();
}
}
}
1-4. MainFragment.java
public class MainFragment extends Fragment {
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
ViewGroup rootView = (ViewGroup) inflater.inflate(R.layout.fragment_main, container, false);
Button button = rootView.findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
MainActivity activity = (MainActivity) getContext();
activity.onFragmentChanged(0);
}
});
return rootView;
}
}
1-5. MenuFragment.java
public class MenuFragment extends Fragment {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
ViewGroup rootView = (ViewGroup) inflater.inflate(R.layout.fragment_menu, container, false);
Button button = rootView.findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
MainActivity activity = (MainActivity) getContext();
activity.onFragmentChanged(1);
}
});
return rootView;
}
}
2. SampleTap
2-1. fragment1.xml (2, 3도 마찬가지)
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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="@android:color/holo_blue_bright"
android:orientation="vertical"
tools:context=".Fragment1">
<Button
android:id="@+id/button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="첫 번째" />
</LinearLayout>
2-2. activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?colorPrimaryDark"
android:elevation="1dp"
android:theme="@style/ThemeOverlay.AppCompat.Dark">
<TextView
android:id="@+id/titleText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="타이틀"
android:textAppearance="@style/Base.TextAppearance.Widget.AppCompat.Toolbar.Title" />
</androidx.appcompat.widget.Toolbar>
<com.google.android.material.tabs.TabLayout
android:id="@+id/tabs"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@android:color/background_light"
android:elevation="1dp"
app:tabGravity="fill"
app:tabMode="fixed"
app:tabSelectedTextColor="?colorAccent"
app:tabTextColor="?colorPrimary" />
</com.google.android.material.appbar.AppBarLayout>
<FrameLayout
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
</FrameLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
</RelativeLayout>
2-3. Fragment1.java (2, 3도 마찬가지)
public class Fragment1 extends Fragment {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment1, container, false);
}
}
2-4. MainActivity.java
public class MainActivity extends AppCompatActivity {
Toolbar toolbar;
Fragment1 fragment1;
Fragment2 fragment2;
Fragment3 fragment3;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
ActionBar actionBar = getSupportActionBar();
actionBar.setDisplayShowTitleEnabled(false);
fragment1 = new Fragment1();
fragment2 = new Fragment2();
fragment3 = new Fragment3();
getSupportFragmentManager().beginTransaction().replace(R.id.container, fragment1).commit();
TabLayout tabs = findViewById(R.id.tabs);
tabs.addTab(tabs.newTab().setText("통화기록"));
tabs.addTab(tabs.newTab().setText("스팸기록"));
tabs.addTab(tabs.newTab().setText("연락처"));
tabs.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
@Override
public void onTabSelected(TabLayout.Tab tab) {
int position = tab.getPosition();
Log.d("MainActivity", "선택된 탭 : " + position);
Fragment selected = null;
if (position == 0) {
selected = fragment1;
} else if (position == 1) {
selected = fragment2;
} else if (position == 2) {
selected = fragment3;
}
getSupportFragmentManager().beginTransaction()
.replace(R.id.container, selected).commit();
}
@Override
public void onTabUnselected(TabLayout.Tab tab) {}
@Override
public void onTabReselected(TabLayout.Tab tab) {}
});
}
}
3. SamplePager (뷰 페이저)
3-1. fragment1.xml (2, 3도 마찬가지)
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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="@android:color/holo_blue_bright"
android:orientation="vertical"
tools:context=".Fragment1">
<Button
android:id="@+id/button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="첫 번째" />
</LinearLayout>
3-2. 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"
android:orientation="vertical"
tools:context=".MainActivity" >
<Button
android:id="@+id/button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="두번째 화면 보여주기" />
<androidx.viewpager.widget.ViewPager
android:id="@+id/pager"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.viewpager.widget.PagerTitleStrip
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="top"
android:background="#55cedf"
android:textColor="#FFFFFF"
android:paddingTop="5dp"
android:paddingBottom="5dp">
</androidx.viewpager.widget.PagerTitleStrip>
</androidx.viewpager.widget.ViewPager>
</LinearLayout>
3-3. Fragment1.java
public class Fragment1 extends Fragment {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment1, container, false);
}
}
3-4. MainActivity.java
public class MainActivity extends AppCompatActivity {
ViewPager pager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
pager = findViewById(R.id.pager);
pager.setOffscreenPageLimit(3);
MyPagerAdapter adapter = new MyPagerAdapter(getSupportFragmentManager());
Fragment1 fragment1 = new Fragment1();
adapter.addItem(fragment1);
Fragment2 fragment2 = new Fragment2();
adapter.addItem(fragment2);
Fragment3 fragment3 = new Fragment3();
adapter.addItem(fragment3);
pager.setAdapter(adapter);
Button button = findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
pager.setCurrentItem(1);
}
});
}
class MyPagerAdapter extends FragmentStatePagerAdapter {
ArrayList<Fragment> items = new ArrayList<Fragment>();
public MyPagerAdapter(FragmentManager fm) {
super(fm);
}
public void addItem(Fragment item) {
items.add(item);
}
@Override
public Fragment getItem(int position) {
return items.get(position);
}
@Override
public int getCount() {
return items.size();
}
@Override
public CharSequence getPageTitle(int position) {
return "페이지 " + position;
}
}
}
4. NavigationDrawer (SampleDrawer)
4-1. fragment1.xml (2, 3도 마찬가지)
<?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"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/holo_blue_bright">
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="첫번째"
android:textSize="40dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
4-2. nav_header_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"
android:layout_width="match_parent"
android:layout_height="@dimen/nav_header_height"
android:background="@drawable/side_nav_bar"
android:gravity="bottom"
android:orientation="vertical"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingBottom="@dimen/activity_vertical_margin"
android:theme="@style/ThemeOverlay.AppCompat.Dark">
<ImageView
android:id="@+id/imageView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:contentDescription="@string/nav_header_desc"
android:paddingTop="@dimen/nav_header_vertical_spacing"
app:srcCompat="@mipmap/ic_launcher_round" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="@dimen/nav_header_vertical_spacing"
android:text="@string/nav_header_title"
android:textAppearance="@style/TextAppearance.AppCompat.Body1" />
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/nav_header_subtitle" />
</LinearLayout>
4-3. activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.drawerlayout.widget.DrawerLayout 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/drawer_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
tools:openDrawer="start">
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:layout_width="match_parent"
android:layout_height="match_parent" >
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="@style/Theme.SampleDrawer.AppBarOverlay">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:popupTheme="@style/Theme.SampleDrawer.PopupOverlay" />
</com.google.android.material.appbar.AppBarLayout>
<FrameLayout
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
</FrameLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
<com.google.android.material.navigation.NavigationView
android:id="@+id/nav_view"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="start"
android:fitsSystemWindows="true"
app:headerLayout="@layout/nav_header_main"
app:menu="@menu/activity_main_drawer" />
</androidx.drawerlayout.widget.DrawerLayout>
4-4. Fragment1.java
public class Fragment1 extends Fragment {
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
ViewGroup rootView = (ViewGroup) inflater.inflate(R.layout.fragment1, container, false);
return rootView;
}
}
4-5. FragmentCallback.java
public interface FragmentCallback {
public void onFragmentSelected(int position, Bundle bundle);
}
4-6. MainActivity.java
public class MainActivity extends AppCompatActivity implements NavigationView.OnNavigationItemSelectedListener, FragmentCallback {
Fragment1 fragment1;
Fragment2 fragment2;
Fragment3 fragment3;
DrawerLayout drawer;
Toolbar toolbar;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
drawer = findViewById(R.id.drawer_layout);
ActionBarDrawerToggle toggle = new ActionBarDrawerToggle(
this, drawer, toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close);
drawer.addDrawerListener(toggle);
toggle.syncState();
NavigationView navigationView = findViewById(R.id.nav_view);
navigationView.setNavigationItemSelectedListener(this);
fragment1 = new Fragment1();
fragment2 = new Fragment2();
fragment3 = new Fragment3();
getSupportFragmentManager().beginTransaction().add(R.id.container, fragment1).commit();
}
@Override
public void onBackPressed() {
if (drawer.isDrawerOpen(GravityCompat.START)) {
drawer.closeDrawer(GravityCompat.START);
} else {
super.onBackPressed();
}
}
@Override
public boolean onNavigationItemSelected(MenuItem item) {
int id = item.getItemId();
if (id == R.id.menu1) {
Toast.makeText(this, "첫번째 메뉴 선택됨.", Toast.LENGTH_LONG).show();
onFragmentSelected(0, null);
} else if (id == R.id.menu2) {
Toast.makeText(this, "두번째 메뉴 선택됨.", Toast.LENGTH_LONG).show();
onFragmentSelected(1, null);
} else if (id == R.id.menu3) {
Toast.makeText(this, "세번째 메뉴 선택됨.", Toast.LENGTH_LONG).show();
onFragmentSelected(2, null);
}
drawer.closeDrawer(GravityCompat.START);
return true;
}
@Override
public void onFragmentSelected(int position, Bundle bundle) {
Fragment curFragment = null;
if (position == 0) {
curFragment = fragment1;
toolbar.setTitle("첫번째 화면");
} else if (position == 1) {
curFragment = fragment2;
toolbar.setTitle("두번째 화면");
} else if (position == 2) {
curFragment = fragment3;
toolbar.setTitle("세번째 화면");
}
getSupportFragmentManager().beginTransaction().replace(R.id.container, curFragment).commit();
}
}
5. [LAB] Fragment들을 사용하여 이미지 변환
5-1. fragment_viewer.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"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/imageView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:srcCompat="@drawable/dream01" />
</LinearLayout>
5-2. fragment_list.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:id="@+id/button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="첫 번째 이미지" />
<Button
android:id="@+id/button2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="두 번째 이미지" />
<Button
android:id="@+id/button3"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="세 번째 이미지" />
</LinearLayout>
5-3. activity_main.xml
<?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">
<fragment
android:id="@+id/listFragment"
android:name="com.example.myapplication.ListFragment"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" />
<fragment
android:id="@+id/viewerFragment"
android:name="com.example.myapplication.ViewerFragment"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" />
</LinearLayout>
5-4. ListFragment.java
// Fragment 클래스를 상속한 ListFragment 생성
public class ListFragment extends Fragment {
// MainActivity.java에서 사용할 인터페이스, 콜백에 사용할 것임
public static interface ImageSelectionCallback {
public void onImageSelected(int position);
}
// 콜백 인스턴스 담을 변수 생성
public ImageSelectionCallback callback;
// Fragment 클래스의 onAttach 메소드 오버라이딩 : 콜백 인스턴스에 context를 지정하여 액티비티 참조(이곳의 context는 얘를 호출하는 녀석, 즉 MainActivity가 될 것임)
@Override
public void onAttach(Context context) {
super.onAttach(context);
if (context instanceof ImageSelectionCallback) {
callback = (ImageSelectionCallback) context;
}
}
// Fragment 클래스의 onCreateView 메소드 오버라이딩 : 해당 뷰 내부요소 구성은 여기서 지정 (MainActivity의 onCreate()와 유사)
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
// 레이아웃을 화면 일부에 표시해주는 LayoutInflater를 포함하여 뷰그룹 지정
ViewGroup rootView = (ViewGroup) inflater.inflate(R.layout.fragment_list, container, false);
// 3개의 버튼 클릭 시의 동작 : 콜백 함수를 통해 onImageSelected(num) 전달
// 버튼 지정 시에 뷰그룹 내에서 찾아야 함, 따라서 rootView.findViewById(...) 로 지정
// 버튼 기능 구현 : callback이 존재한다면(즉 not null), callback에게 어떠한 함수를 요청한다.
// Hint : callback 의 타입은 ImageSelectionCallback 이다. 해당 인터페이스의 선언문으로 가보자.
// Hint 2 : 이 파일 상단에 있다.
Button button = rootView.findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (callback != null) {
callback.onImageSelected(1);
}
}
});
Button button2 = rootView.findViewById(R.id.button2);
button2.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (callback != null) {
callback.onImageSelected(2);
}
}
});
Button button3 = rootView.findViewById(R.id.button3);
button3.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (callback != null) {
callback.onImageSelected(3);
}
}
});
return rootView;
}
}
5-5. ViewerFragment.java
// 이미지를 표시해주는 Fragment
public class ViewerFragment extends Fragment {
ImageView imageView;
// 뷰가 생성될 때의 동작(MainActivity의 onCreate()와 유사)
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
ViewGroup rootView = (ViewGroup) inflater.inflate(R.layout.fragment_viewer, container, false);
imageView = rootView.findViewById(R.id.imageView);
return rootView;
}
// 이미지 세팅해주는 알맹이 함수
public void setImage(int resId) {
imageView.setImageResource(resId);
}
}
5-6. MainActivity.java
// MainActivity는 ListFragment.ImageSelectionCallback 인터페이스를 implement한다.
public class MainActivity extends AppCompatActivity implements ListFragment.ImageSelectionCallback {
// 리스트 Fragment 담는 변수
ListFragment listFragment;
// 이미지 Fragment 담는 변수
ViewerFragment viewerFragment;
// MainActivity에서 image들을 받아다가 여기서 인자로 넘길 때 사용
int[] images = {R.drawable.dream01, R.drawable.dream02, R.drawable.dream03};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// FragmentManager 변수를 통해 Fragment 관리
FragmentManager manager = getSupportFragmentManager();
// id를 집어넣어서 변수에 인스턴스를 추가합시다
listFragment = (ListFragment) manager.findFragmentById(R.id.listFragment);
viewerFragment = (ViewerFragment) manager.findFragmentById(R.id.viewerFragment);
}
// ListFragment.ImageSelectionCallback 인터페이스 implement에 의해 선언된 오버라이드 메소드
// Hint : ListFragment.java 파일에서 버튼 동작을 작성했다면, 여기서 그 구체적인 행동을 지정하는 것
// Hint 2 : 어느 이미지를 고를지를 ListFragment에서 position 인자로 넘겼으니, 그걸 설정하는 Fragment는 누굴까? 그녀석의 메소드를 불러올 필요가 있을 것이다.
@Override
public void onImageSelected(int position) {
if (position == 1) {
viewerFragment.setImage(images[0]);
} else if (position == 2) {
viewerFragment.setImage(images[1]);
} else if (position == 3) {
viewerFragment.setImage(images[2]);
}
}
}
'안드로이드 > 연습 코드' 카테고리의 다른 글
6장. 서비스와 브로드캐스트 리시버 (0) | 2023.11.07 |
---|---|
4장. Activity와 Intent (0) | 2023.11.07 |
3장. 위젯 (0) | 2023.11.07 |
2장. 뷰와 레이아웃 (0) | 2023.11.07 |
1장. URL 버튼 만들기 (0) | 2023.11.07 |