링크연결
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse("http://m.naver.com"));
startActivity(intent);
1. 레이아웃
환경설정 및 기본속성
android studio: IntelliJ 에서 발전된 개발도구
다운로드링크: https://developer.android.com/studio?gclid=CjwKCAjwos-HBhB3EiwAe4xM91psnsUbokVvgwEWcNRh-BxSys_fKIvvB6wOoWJ5ZnKqZk_rzkfX0BoCt10QAvD_BwE&gclsrc=aw.ds{:target="_blank"}
help - check for update: 최신버전 확인
루트디렉토리에 .android 폴더가 생성된다.
패키지 생성 시 Name: 앱이름이 되며 파스칼표기법 혹은 띄어쓰기를 활용한다.
AVD Manager 를 통해 가상단말을 추가한다.
폰트설정: file - settings - editor - font
오토임폴트: file - settings - editor - general - auto import -
- add unambiguous imports on the fly: import 문 자동추가
- optimize imports on the fly: 불필요한 import문을 제거하여 자동으로 최적화
도구사용
테마변경: Appearance&Behavior - Appearance - Theme
gradle 파일 변경저장: Sync project with gradle files
ctrl + Q: 설명문서
ctrl + shift + F: 검색
design 과 blueprint 를 볼 수 있다.
뷰의속성
뷰 (View): 화면에 보이는 것들. 흔히 컨트롤이나 위젯이라 불리는 UI 구성요소
뷰그룹 (View Group)
위젯 (Widget): 뷰 중에서 일반적인 컨트롤의 역할을 하고있는 것
레이아웃 (Layout): 뷰 그룹 중에서 내부에 뷰들을 포함하고 있으면서 그것들을 배치하는 역할을 하는 것
레이아웃 안에 레이아웃을 포함시킬 수 있다.
필수속성: layout_width
, layout_height
match_parent
부모와 같게.
wrap_content
내용을 감싼다.
ConstraintLayout
LinearLayout
orientation 이 필수
단위
px: 픽셀
dp or dip: 밀도 독립적 픽셀 (density independent pixel). 화면이 클수록 크게 표현한다.
예) 1인치 당 160개 점이있는 디스플레이화면에서 1dp = 1px
1인치 당 320 개의 점이있는 디스플레이화면에서 1dp = 2px
sp or sip: 축적 독립적 픽셀 (scale independent pixel).
in: 인치
mm: 밀리미터
em: 글꼴과 상관없이 동일한 텍스트크기 표시
보통은 dp 로 모두 작성
Constraint Layout: 제약 레이아웃
autoconnection to parent: 자석모양. 자동연결된다.
bias: 위치조정
guidelines 을 만들 수 있다.
레이아웃 종류
대표레이아웃
레이아웃 | 설명 |
---|---|
Constraint | 제약조건을 사용해 화면구성 최근에 나온 레이아웃 |
Linear | 박스모델 한쪽방향으로 차례대로 뷰를 추가하며 화면구성 |
Relative | 규칙기반모델 부모컨테이너나 다른뷰와 상대적위치로 화면구성 |
Frame | 싱글모델 가장 상위에있는 하나의 뷰 또는 뷰그룹만 보여주는 방법 여러개의 뷰가 들어가면 중첩하여 쌓게됨. 여러개의 뷰를 중첩한 후 각 뷰를 전환하여 보여주는 방식 |
Table | 격자모델 HTML에서 많이 사용하는 정렬방식과 유사하지만 많이 사용하지는 않음 |
뷰의영역 (box) 는 패딩 (padding) 과 테두리 (border) 와 마진 (margin) 까지를 포함한다.
background 의 앞 두자리는 투명도이다. 00
ff 까지 투명
원색
layout_gravity
뷰 정렬
gravity
내용 정렬
layout-margin
padding
layout_weight
남아있는 공간분할
Relative Layout: 상대 레이아웃
layout_alignParentTop
Bottom
Left
Right
부모에 상대적으로 연결
layout_alignTop
Bottom
Left
Right
뷰에 상대적으로 연결
Frame Layout: 프레임 레이아웃
뷰의 가시성 (visibility) 을 이용한다.
ImageView imageView = (ImageView) findViewById(R.id.imageView);
R: resource
리소스에 있는 imageView 라는 id 를 찾아서 형변환 (캐스팅) 하여 imageView 변수에 넣습니다.
imageView.setVisibility(View.VISIBLE);
visibility 를 제어한다.
버튼클릭으로 이미지변환
public void clicked(View view) {
if (bool == true) {
bool = false;
} else {
bool = true;
}
if (bool) {
imageView.setVisibility(View.VISIBLE);
imageView2.setVisibility(view.INVISIBLE);
} else {
imageView.setVisibility(View.INVISIBLE);
imageView2.setVisibility(view.VISIBLE);
}
}
기본위젯
텍스트뷰 속성
text
문자textColor
색상textSize
크기textStyle
스타일속성typeFace
폰트설정normal
sans
serif
monospace
maxLines="1"
한 줄로만 표시
radioButton
isChecked
드로어블
상태 드로어블
새로운 xml 파일을 만든다.
drawable
state_pressed
state_focused
<item
android:state_pressed="true"
android:drawable="@drawable/ic_thumb_up_selected"/>
<item
android:drawable="@drawable/ic_thumb_up"/>
쉐이프 드로어블
selector 를 shape 로 바꾼다.
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<size android:width="200dp"
android:height="120dp"/>
<stroke android:width="1dp"
android:color="#00f"/>
<solid android:color="#adf"/>
<padding android:bottom="1dp"/>
</shape>
<Shape
shape
내부속성
size
stroke
solid
테두리padding
내부여백gradient
startColor
centerColor
endColor
angle
centerY
corners
radius
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<gradient
android:startColor="#7288DB"
android:centerColor="#3250B4"
android:endColor="#254095"
android:angle="90"
android:centerY="0.5"
/>
<corners android:radius="2dp"
/>
</shape>
테두리
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<shape android:shape="rectangle">
<stroke android:width="1dp"
android:color="#BE55DA"/>
<solid android:color="#0000"/>
</shape>
</item>
<item android:top="1dp"
android:bottom="1dp"
android:right="1dp"
android:left="1dp">
<shape android:shape="rectangle">
<stroke android:width="1dp"
android:color="#BE55DA"/>
<solid android:color="#0000"/>
</shape>
</item>
</layer-list>
앱 정보
manifests 폴더의 AndroidManifest.xml 에서
icon
앱 아이콘
label
앱 이름
2. 이벤트와 리스트뷰
이벤트
테이블 레이아웃
격자로 추가할 수 있다.
stretchColumns
갯수만큼 0, 1, 2
해주면 크기를 동일하게 나눠 갖는다.
layout_column
0
1
2
layout_span
몇 칸 차지할지 정할 수 있다.
스크롤뷰
<ScrollView>
로 감싸주면 된다.
이벤트처리
- 터치 이벤트
- 키 이벤트
- 제스처 이벤트
- 포커스
- 화면방향 변경
메서드 | 이벤트 |
---|---|
onDown | 눌렀을 경우 |
onShowPress | 눌렀다 떼어지는 경우 |
onSingleTapUp | 한손가락으로 눌르는 경우 |
onDoubleTap | |
onDoubleTabEvent | 두 손가락이 눌러진 상태에서 떼거나 이동하는 세부적인 액션 |
onScroll | 화면을 누른 채 일정한 속도와 방향으로 움직였다 떼는 경우 |
onFling | 화면을 누른 채 가속도를 붙여 손가락을 움직였다 떼는 경우 |
onLongPress | 화면을 손가락으로 오래 누르는 경우 |
textView
append
글자 추가
public void println(String data) {
textView.append(data + "\n");
}
터치 감지
view.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, 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;
}
});
제스처 감지
detector = new GestureDetector(this, new GestureDetector.OnGestureListener() {
@Override
public boolean onDown(MotionEvent e) {
println("onDown() 호출됨");
return true;
}
@Override
public void onShowPress(MotionEvent e) {
println("ShowPress");
}
@Override
public boolean onSingleTapUp(MotionEvent e) {
println("SingleTapUp");
return true;
}
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
println("Scroll" + distanceX + ", " + distanceY);
return true;
}
@Override
public void onLongPress(MotionEvent e) {
println("LongPress");
}
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
println("Fling" + velocityX + ", " + velocityY);
return true;
}
});
view2.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
detector.onTouchEvent(event);
return true;
}
});
키 입력 이벤트 처리
키 코드 | 설명 |
---|---|
keycode_dpad_left | 왼쪽 화살표 |
keycode_dpad_right | 오른쪽 화살표 |
keycode_dpad_up | 위쪽 화살표 |
keycode_dpad_down | 아래쪽 화살표 |
keycode_dpad_center | 중앙 버튼 |
keycode_call | 통화 버튼 |
keycode_endcall | 통화종료 버튼 |
keycode_home | 홈 버튼 |
keycode_back | 뒤로가기 버튼 |
keycode_volume_up | 소리크기증가 버튼 |
keycode_volume_down | 소리크기감소 버튼 |
keycode_0~keycode_9 | 숫자 0부터 9까지의 키 값 |
keycode_A~keycode_Z | 알파벳 A 부터 Z 까지의 키 값 |
ctrl + O (메서드 오버라이딩) 을 통해 정의
뒤로가기 버튼
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK) {
Toast.makeText(this, "시스템 Back 버튼눌림", Toast.LENGTH_LONG).show();
}
return true;
}
무료아이콘 사이트
아이콘파인더: https://www.iconfinder.com/{:target="_blank"}
토스트
setGravity
setMargin
클릭: 셋온클릭리스너(뉴 뷰.온클릭리스너: . - new - V)
토스트 위치바꾸기
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast toast = Toast.makeText(getApplicationContext(), "위치가 바뀐 토스트", Toast.LENGTH_LONG);
toast.setGravity(Gravity.TOP|Gravity.LEFT, 0, 0);
toast.show();
}
});
LayoutInflater inflater = getLayoutInflater();
inflater.inflate(layout res, root layout id)
레이아웃을 찾습니다.
토스트 꾸미기
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<stroke
android:width="4dp"
android:color="#ffffff00"/>
<solid
android:color="#ff883300"/>
<padding
android:left="20dp"
android:top="20dp"
android:right="20dp"
android:bottom="20dp"/>
<corners
android:radius="15dp"/>
</shape>
토스트
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/toast_layout_root">
<TextView
android:id="@+id/text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="20dp"
android:textSize="40dp"
android:background="@drawable/toast"/>
</LinearLayout>
버튼클릭 시 토스트
button2.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
LayoutInflater inflater = getLayoutInflater();
View layout = inflater.inflate(R.layout.toastborder, (ViewGroup) findViewById(R.id.toast_layout_root));
TextView text = layout.findViewById(R.id.text);
text.setText("모양을 바꾼 토스트");
Toast toast = new Toast(getApplicationContext());
toast.setGravity(Gravity.CENTER, 100, 100);
toast.setDuration(Toast.LENGTH_LONG);
toast.setView(layout);
toast.show();
}
});
스낵바
file - project structure - dependencies - "+" - library dependency - design 이라고 검색하면
com.android.support 라고 있다.
선택하여 ok 를 눌러주면
gradle scripts 의 build.gradle (module: event-4.app) 의 dependencies 에 design 이
implementation 된 것을 볼 수 있다.
저는 외부 라이브러리가 되지않아 삭제하고 내장된 come.google.android.material.snackbar 를 이용했습니다.
Button button3 = findViewById(R.id.button3);
button3.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Snackbar.make(v, "스낵바입니다.", Snackbar.LENGTH_LONG).show();
}
});
알림대화상자
public void showMessage() {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle("안내");
builder.setMessage("종료하시겠습니까?");
builder.setIcon(android.R.drawable.ic_dialog_alert);
builder.setPositiveButton("예", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
Snackbar.make(textView, "예 버튼이 눌렸습니다.", Snackbar.LENGTH_LONG).show();
}
});
builder.setNegativeButton("아니오", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
Snackbar.make(textView, "아니오 버튼이 눌렸습니다.", Snackbar.LENGTH_LONG).show();
}
});
AlertDialog dialog = builder.create();
dialog.show();
}
나인패치
나인패치 이미지
Nine patch 이미지란?
- 이미지가 늘어나거나 줄어들 때 생기는 이미지 왜곡을 해결하는 방법을 정의한 것
- 서로 다른 해상도를 가진 여러 단말에 dp 단위로 뷰의 크기를 맞추다 보면 이미지 크기가 자동조절되면서 왜곡되는 현상발생 -> 나인패치 이미지로 해결한다.
나인패치 정식 가이드: https://developer.android.com/studio/write/draw9patch.html?hl=ko{:target="_blank"}
나인패치 비공식 가이드: https://recipes4dev.tistory.com/132{:target="_blank"}
나인패치 만드는 사이트: http://romannurik.github.io/AndroidAssetStudio/nine-patches.html#&sourceDensity=320&name=example{:target="_blank"}
비트맵 버튼
나인패치 이미지를 적용하는 대표적인 경우가 바로 버튼이다.
하지만 이런 버튼은 아무리 눌러도 이미지의 변화가 없어 사용자가 버튼을 눌렀는지 알 수 없다는 단점이 있다.
비트맵 이미지를 이용해 버튼의 상태를 표시하려면 버튼이 눌렸을 때와 떼어졌을 때를 이벤트로 구분하여 처리한다.
onMeasure
onDraw
setMeasuredDimension
뷰에 그래픽이 그려질 때 onDraw() 메소드가 호출됨
다시 그리기는 invalidate() 메소드를 사용함
AppCompatButton 의 필수 생성자
- 파라미터 하나: 자바에서 버튼생성
- 파라미터 둘: layout.xml 로 버튼생성
자바 소스코드에서는 px 단위로 텍스트크기가 설정된다.
설정하고 싶다면 values 폴더에 xml 파일을 만들어 불러온다.
<resources>
<dimen name="text_size">16dp</dimen>
</resources>
비트맵 버튼
public class BitmapButton extends AppCompatButton {
public BitmapButton(Context context) {
super(context);
init(context);
}
public BitmapButton(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
private void init(Context context) {
setBackgroundResource(R.drawable.title_bitmap_button_normal);
float textSize = getResources().getDimension(R.dimen.text_size);
setTextSize(textSize);
setTextColor(Color.WHITE);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
int action = event.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN:
setBackgroundResource(R.drawable.title_bitmap_button_clicked);
break;
case MotionEvent.ACTION_UP:
setBackgroundResource(R.drawable.title_bitmap_button_normal);
break;
}
invalidate();
return true;
}
}
버튼은 <com.example.mybutton.BitmapButton>
로 선언해야 한다.
어댑터 및 인플레이션
인플레이션
XML 레이아웃에 정의된 내용이 메모리에 객체화되는 과정
안드로이드가 버튼과 같은 것을 만들어준다.
setContentView(R.layout.activity_main)
xml 파일을 화면으로 만들어줍니다.
전체화면이 아닌경우 LayoutInflater
클래스를 사용해 사용자가 직접 인플레이트를 해야한다.
FrameLayout container = findViewById(R.id.container);
Button button = findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
LayoutInflater inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
inflater.inflate(R.layout.sub1, container, true);
}
});
inflater.inflate(R.layout.부분 레이아웃, 부모 레이아웃 id, true)
리스트뷰
여러 개의 아이템중에서 하나를 선택하는 방식의 선택위젯은 어댑터를 사용하여야 한다.
이 어댑터에서 데이터를 관리하도록 해야할 뿐만 아니라 화면에 보여지는 뷰도 어댑터의 getView() 메소드에서 결정한다.
선택위젯의 가장 큰 특징은 원본 데이터를 위젯에 직접 설정하지 않고 어댑터라는 클래스를 사용하도록 되어있다는 점이다.
- 리스트뷰
- 스피너: 콤보박스
- 그리드뷰
- 갤러리
리스트뷰로 보여줄 때 해야할 일들
- 리스트뷰에 들어갈 각 아이템의 레이아웃을 XML 로 정의한다.
- 인플레이션 후 설정해야 한다.
- 데이터관리 역할을 하는 어댑터 클래스를 만들고 그 안에 각 아이템으로 표시할 뷰를 리턴하는 getView() 메소드를 정의한다.
- 화면에 보여줄 리스트뷰를 만들고 그 안에 데이터가 선택되었을 때 호출될 리스너 객체를 정의한다.
어댑터
BaseAdapter
를 상속한다.
데이터를 관리한다.
만드는 법
- 객체클래스를 생성 후 생성자, getter, setter, toString 을 작성
- singer_item.xml 리스트뷰 디자인
- singer_item 을 inflate
- BaseAdapter 를 상속하는 어댑터클래스 생성 후 객체리스트 작성
객체
public class SingerItem {
String name;
String mobile;
int resId;
public SingerItem(String name, String mobile, int resId) {
this.name = name;
this.mobile = mobile;
this.resId = resId;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getResId() {
return resId;
}
public String getMobile() {
return mobile;
}
public void setMobile(String mobile) {
this.mobile = mobile;
}
public int getImage() {
return resId;
}
@Override
public String toString() {
return "SingerItem{" +
"name='" + name + '\'' +
", mobile='" + mobile + '\'' +
'}';
}
}
뷰 레이아웃
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageView
android:id="@+id/imageView"
android:layout_width="80dp"
android:layout_height="80dp"
android:src="@mipmap/ic_launcher"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="이름"
android:textSize="30dp"
android:textColor="@color/design_default_color_primary_dark"/>
<TextView
android:id="@+id/textView2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="전화번호"
android:textColor="@android:color/holo_orange_light"
android:layout_marginTop="3dp"
android:textSize="30dp" />
</LinearLayout>
</LinearLayout>
inflate
public class SingerItemView extends LinearLayout {
TextView textView;
TextView textView2;
ImageView imageView;
public SingerItemView(Context context) {
super(context);
init(context);
}
public SingerItemView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init(context);
}
private void init(Context context) {
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
inflater.inflate(R.layout.singer_item, this, true);
textView = findViewById(R.id.textView);
textView2 = findViewById(R.id.textView2);
imageView = findViewById(R.id.imageView);
}
public void setName(String name) {
textView.setText(name);
}
public void setMobile(String mobile) {
textView2.setText(mobile);
}
public void setImage(int resId) {
imageView.setImageResource(resId);
}
}
리스트뷰
public class MainActivity extends AppCompatActivity {
SingerAdapter adapter;
EditText editText;
EditText editText2;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ListView listView = findViewById(R.id.listView);
adapter = new SingerAdapter();
adapter.addItem(new SingerItem("소녀시대", "010-1000-1000", android.R.drawable.ic_lock_idle_lock));
adapter.addItem(new SingerItem("동방신기", "010-2000-1000", android.R.drawable.ic_btn_speak_now));
adapter.addItem(new SingerItem("걸스데이", "010-3000-1000", android.R.drawable.ic_lock_silent_mode));
adapter.addItem(new SingerItem("여자친구", "010-4000-1000", android.R.drawable.ic_dialog_email));
adapter.addItem(new SingerItem("티아라", "010-5000-1000", android.R.drawable.ic_lock_idle_alarm));
listView.setAdapter(adapter);
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
SingerItem item = (SingerItem) adapter.getItem(position);
Toast.makeText(getApplicationContext(), "선택: " + item.getName(), Toast.LENGTH_LONG).show();
}
});
editText = findViewById(R.id.editText);
editText2 = findViewById(R.id.editText2);
Button button = findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String name = editText.getText().toString();
String mobile = editText2.getText().toString();
adapter.addItem(new SingerItem(name, mobile, R.drawable.ic_launcher_foreground));
adapter.notifyDataSetChanged();
}
});
}
class SingerAdapter extends BaseAdapter {
ArrayList<SingerItem> items = new ArrayList<>();
@Override
public int getCount() {
return items.size();
}
@Override
public Object getItem(int position) {
return items.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
SingerItemView view = null;
if (convertView == null) {
view = new SingerItemView(getApplicationContext());
} else {
view = (SingerItemView) convertView;
}
SingerItem item = items.get(position);
view.setName(item.getName());
view.setMobile(item.getMobile());
view.setImage(item.getImage());
return view;
}
public void addItem(SingerItem item) {
items.add(item);
}
}
}
요소 추가하는 법
- 객체에 속성, 생성자, getter 추가
- 디자인에 id 추가
- 속성, init, setter 추가
- getView 추가
뷰는 setter, 객체는 getter
클릭 시 토스트
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
SingerItem item = (SingerItem) adapter.getItem(position);
Toast.makeText(getApplicationContext(), "선택: " + item.getName(), Toast.LENGTH_LONG).show();
}
});
버튼클릭 시 추가
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String name = editText.getText().toString();
String mobile = editText2.getText().toString();
adapter.addItem(new SingerItem(name, mobile, R.drawable.ic_launcher_foreground));
adapter.notifyDataSetChanged();
}
});
뷰 돌려쓰기
SingerItemView view = null;
if (convertView == null) {
view = new SingerItemView(getApplicationContext());
} else {
view = (SingerItemView) convertView;
}
스피너
public class MainActivity extends AppCompatActivity {
TextView textView;
String[] items = {"소녀시대", "걸스데이", "티아라", "블랙핑크", "여자친구", "동방신기"};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
textView = findViewById(R.id.textView);
Spinner spinner = findViewById(R.id.spinner);
ArrayAdapter<String> adapter = new ArrayAdapter<>(
this, android.R.layout.simple_spinner_item, items
);
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
spinner.setAdapter(adapter);
spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
textView.setText(items[position]);
}
@Override
public void onNothingSelected(AdapterView<?> parent) {
textView.setText("선택: ");
}
});
}
}
그리드뷰
numColumns
격자의 칼럼갯수. 1이라면 리스트뷰와 차이가 없다.
리스트뷰와 구현방식이 똑같다.
setText
textView.setText(String)
파라미터로 String 형만 받으므로 int 형을 넣고싶을 때는String.valueOf()
를 사용한다.
프로필 둥글게 만들기
build.gradle(Module) - dependencies 에 라이브러리 추가
implementation 'de.hdodenhof:circleimageview:2.2.0'
<de.hdodenhof.circleimageview.CircleImageView
android:layout_width="90dp"
android:layout_height="90dp"
android:layout_marginStart="16dp"
android:layout_marginLeft="16dp"
android:layout_marginTop="16dp"
android:src="@drawable/ic_launcher_background"
app:civ_border_color="#ffcccccc"
app:civ_border_width="2dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
위와 같이 선언한다.
inflate 요약
- L i = sy Co. 후 캐스팅
- i. R.l.레이아웃 뷰
리시코 아이 알 엘
FrameLayout view = findViewById(R.id.view);
LayoutInflater inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
inflater.inflate(R.layout.xml, view);
FrameLayout 은 바로 추가할 수 있다.
뷰 생성
- 클래스 생성 후 LinearLayout 상속받는다.
- 생성자 2개 생성 후 init(content) 메서드 호출
버튼생성 요약
- B b = f R.id.
- b. new
버튼.셋온클릭리스너(뉴 뷰.온클릭리스너)
3. 여러 개의 화면
네 가지 구성요소
- 액티비티
- 서비스
- 브로드캐스트 수신자
- 내용 제공자
액티비티
돌아올 때 데이터 받기
Main 화면
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 v) {
Intent intent = new Intent(getApplicationContext(), MenuActivity.class);
startActivityForResult(intent, 101);
}
});
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == 101) {
String name = data.getStringExtra("name");
Toast.makeText(getApplicationContext(), "메뉴화면으로부터 응답: " + name, Toast.LENGTH_LONG).show();
}
}
}
- Intent i = new g , 액티비티.
오버라이딩: acr 검색 (onActivityResult)
Menu 화면
button2.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent();
intent.putExtra("name", "mike");
setResult(Activity.RESULT_OK, intent);
finish();
}
});
Manifest
<activity android:name=".MenuActivity"
android:label="메뉴"
android:theme="@style/Theme.AppCompat.Light.Dialog"></activity>
인텐트
액티비티, 서비스, 브로드캐스트 수신자끼리 이동하게 해준다. 화면이동 그 이상이라고 할 수 있다.
속성 | 설명 |
---|---|
ACTION_DIAL tel:01077881234 | 주어진 전화번호를 이용해 전화걸기 화면을 보여줌 |
ACTION_VIEW tel:01077881234 | 주어진 전화번호를 이용해 전화걸기 화면을 보여줌. URI 값의 유형에 따라 VIEW 액션이 다른 기능을 수행함 |
ACTION_EDIT content://contacts/people/2 | 전화번호부 데이터베이스에 있는 정보중에서 ID 값이 2인 정보를 편집하기 위한 화면을 보여줌 |
ACTION_VIEW content://contacts/people | 전화번호부 데이터베이스의 내용을 보여줌 |
명시적 인텐트 (Explicit Intent)
- 인텐트에 클래스 객체나 컴포넌트 이름을 지정하여 호출할 대상을 확실히 알 수 있는경우
암시적 인텐트 (Implicit Intent)
- 액션과 데이터를 지정하긴 햇지만 호출할 대상이 달라질 수 있는경우
- 범주 (category), 타입 (type), 컴포넌트 (component), 부가데이터 (extras)
전화걸기
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String receiver = editText.getText().toString();
Intent intent = new Intent(Intent.ACTION_DIAL, Uri.parse("tel:" + receiver));
startActivity(intent);
}
});
인텐트로 화면전환이 아닌 전화걸기라는 서비스로 연결해준 것을 볼 수 있다.
화면이동
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent2 =new Intent();
ComponentName name = new ComponentName("com.example.a11mycallintent", "com.example.a11mycallintent.MenuActivity");
intent2.setComponent(name);
startActivity(intent2);
}
});
객체가 아닌 문자열로 화면이동이 가능하다.
인텐트로 PDF 파일을 보여줄 수 있다. 그러려면 pdf 파일을 앱에 다운로드 받아야 한다.
읽기/쓰기 권한
Manifest
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
하지만 이것은 위험권한이다.
targetSdkVersion 을 22 보다 낮게하면 위험권한 감지가 되지 않는다.
부가데이터
액티비티는 스택구조이다.
- 새로운 액티비티를 실행할 때마다 메모리에 새로운 객체를 만들고 이전화면 위에 쌓는 방식은 비효율적일 수 있다.
- 동일한 화면이 이미 만들어져 있는 경우에는 그 화면을 그대로 보여주고 싶다면 플래그를 사용하면 된다.
FLAG_ACTIVITY_SINGLE_TOP
FLAG_ACTIVITY_NO_HISTORY
FLAG_ACTIVITY_CLEAR_TOP
플래그 사용 예
- 인텐트객체 생성
- 부가데이터 넣기
- 인텐트플래그 설정
- 인텐트 띄우기
실무에서는 clear top 과 single top 을 많이 쓴다.
MyParcelable
데이터 넘겨주기
intent.putExtra("names", names)
데이터 넘겨받기
private void processIntent(Intent intent) {
if (intent != null) {
ArrayList<String> names = (ArrayList<String>) intent.getSerializableExtra("names");
if (names != null) {
Toast.makeText(getApplicationContext(), "전달받은 이름 리스트 갯수: " + names.size(), Toast.LENGTH_LONG).show();
}
}
}
getIntent() 로 받아서 토스트를 띄운다.
파슬은 데이터를 전달할 때 사용되는 객체
- 변수선언 및 생성자
- writeToParcel 구현
객체 데이터
public class SimpleData implements Parcelable {
int number;
String message;
public SimpleData(int number, String message) {
this.number = number;
this.message = message;
}
protected SimpleData(Parcel in) {
number = in.readInt();
message = in.readString();
}
public static final Creator<SimpleData> CREATOR = new Creator<SimpleData>() {
@Override
public SimpleData createFromParcel(Parcel in) {
return new SimpleData(in);
}
@Override
public SimpleData[] newArray(int size) {
return new SimpleData[size];
}
};
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(number);
dest.writeString(message);
}
}
객체 데이터 넘겨주기
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(getApplicationContext(), MenuActivity.class);
SimpleData data = new SimpleData(100, "Hello");
intent.putExtra("data", data);
startActivityForResult(intent, 101);
}
});
객체 데이터 받기
getIntent 로 받은 후
private void processIntent(Intent intent) {
if (intent != null) {
SimpleData data = intent.getParcelableExtra("data");
if (data != null) {
Toast.makeText(getApplicationContext(), "전달받은 SimpleData: " + data.message, Toast.LENGTH_LONG).show();
}
}
}
액티비티 수명주기
상태 | 설명 |
---|---|
실행 (Running) | 화면상에 액티비티가 보이면서 실행되어있는 상태 |
일시중지 (Paused) | 사용자에게 보이지만 다른 액티비티가 위에있어 포커스 받지 못하는 상태. 대화상자가 위에 있어 일부가 가려져있는 경우에 해당함 |
중지 (Stopped) | 다른 액티비티에 의해 완전히 가려져 보이지 않는 상태 |
토스트 요약
Toast.makeText(this, "Hello", Toast.LENGTH_LONG).show();
To. t, 텍스트, T탭.탭 .탭
오버라이딩
@Override
protected void onRestart() {
Toast.makeText(this, "리스타트", Toast.LENGTH_LONG).show();
super.onRestart();
}
@Override
protected void onPause() {
Toast.makeText(this, "퍼즈", Toast.LENGTH_LONG).show();
super.onPause();
}
@Override
protected void onResume() {
Toast.makeText(this, "리쥼", Toast.LENGTH_LONG).show();
super.onResume();
}
@Override
protected void onStart() {
Toast.makeText(this, "스타트", Toast.LENGTH_LONG).show();
super.onStart();
}
@Override
protected void onStop() {
Toast.makeText(this, "스탑", Toast.LENGTH_LONG).show();
super.onStop();
}
@Override
protected void onDestroy() {
Toast.makeText(this, "디스트로이", Toast.LENGTH_LONG).show();
super.onDestroy();
}
앱을 끄는 순간 저장하기
@Override
protected void onPause() {
super.onPause();
Toast.makeText(this, "퍼즈", Toast.LENGTH_LONG).show();
SharedPreferences pref = getSharedPreferences("pref", Activity.MODE_PRIVATE);
SharedPreferences.Editor editor = pref.edit();
editor.putString("name", "소녀시대");
editor.commit();
}
@Override
protected void onResume() {
super.onResume();
Toast.makeText(this, "리쥼", Toast.LENGTH_LONG).show();
SharedPreferences pref = getSharedPreferences("pref", Activity.MODE_PRIVATE);
if (pref != null) {
String name = pref.getString("name", "");
Toast.makeText(this, "복구된 이름: " + name, Toast.LENGTH_LONG).show();
}
}
서비스
- 서비스는 화면이 없는 상태에서 백그라운드로 실행됨
- 서비스는 프로세스가 종료되어도 시스템에서 자동으로 재시작됨
new - service - service 메뉴를 이용해 서비스 추가
서비스는 한 번 실행되면 계속 실행되어있다.
onCreate 에서 확인할 수 없다.
그래서 서비스는 onStartCommand 에서 확인할 수 있다.
인텐트를 StartCommand 에 넣는다.
public class MyService extends Service {
private static final String TAG = "MyService";
public MyService() {
}
@Override
public void onCreate() {
super.onCreate();
Log.d(TAG, "onCreate() 호출됨.");
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.d(TAG, "onStartCommand() 호출됨.");
if (intent == null) {
return Service.START_STICKY;
} else {
processCommand(intent);
}
return super.onStartCommand(intent, flags, startId);
}
private void processCommand(Intent intent) {
String command = intent.getStringExtra("command");
String name = intent.getStringExtra("name");
Log.d(TAG, "전달받은 데이터: " + command + ", " + name);
}
@Override
public void onDestroy() {
super.onDestroy();
Log.d(TAG, "onDestroy() 호출됨.");
}
@Override
public IBinder onBind(Intent intent) {
// TODO: Return the communication channel to the service.
throw new UnsupportedOperationException("Not yet implemented");
}
}
Logcat 에서 로그를 확인할 수 있다.
android monitor 에서 서비스를 확인할 수 있다.
화면이 없는 상태에서 띄우려면 flag 가 필요하다.
Main
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
EditText editText = findViewById(R.id.editText);
Button button = findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String name = editText.getText().toString();
Intent intent = new Intent(getApplicationContext(), MyService.class);
intent.putExtra("command", "show");
intent.putExtra("name", name);
startService(intent);
}
});
Intent passedIntent = getIntent();
processCommand(passedIntent);
}
private void processCommand(Intent intent) {
if (intent != null) {
String command = intent.getStringExtra("command");
String name = intent.getStringExtra("name");
Toast.makeText(this, "서비스로부터 전달받은 데이터: ", Toast.LENGTH_LONG).show();
}
}
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
processCommand(intent);
}
}
Service
public class MyService extends Service {
private static final String TAG = "MyService";
public MyService() {
}
@Override
public void onCreate() {
super.onCreate();
Log.d(TAG, "onCreate() 호출됨.");
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.d(TAG, "onStartCommand() 호출됨.");
if (intent == null) {
return Service.START_STICKY;
} else {
processCommand(intent);
}
return super.onStartCommand(intent, flags, startId);
}
private void processCommand(Intent intent) {
String command = intent.getStringExtra("command");
String name = intent.getStringExtra("name");
Log.d(TAG, "전달받은 데이터: " + command + ", " + name);
try {
Thread.sleep(5000);
} catch (Exception e) {
}
Intent showIntent = new Intent(getApplicationContext(), MainActivity.class);
showIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK|
Intent.FLAG_ACTIVITY_SINGLE_TOP|
Intent.FLAG_ACTIVITY_CLEAR_TOP);
showIntent.putExtra("command", "show");
showIntent.putExtra("name", name + " from service.");
startActivity(showIntent);
}
@Override
public void onDestroy() {
super.onDestroy();
Log.d(TAG, "onDestroy() 호출됨.");
}
@Override
public IBinder onBind(Intent intent) {
// TODO: Return the communication channel to the service.
throw new UnsupportedOperationException("Not yet implemented");
}
}
브로드캐스트 수신자
- 애플리케이션이 글로벌이벤트를 받아서 처리하려면 브로드캐스트 수신자로 등록
- "전화가 왔습니다." "문자 메시지가 도착했습니다." 와 같이 안드로이드 시스템 전체에 보내지는 이벤트
- 브로드캐스트 수신자는 인텐트필터를 포함하며 매니페스트 파일에 등록함으로써 인텐트를 받을 준비를 함
- 수신자가 매니페스트 파일에 등록되었다면 따로 시작시키지 않아도 됨
- 애플리케이션은 컨텍스트 클래스의 registerReceiver 메소드를 이용하면 런타임시에도 수신자를 등록할 수 있음
- 서비스처럼 브로드캐스트 수신자도 UI 가 없음
크게 두가지 클래스로 구분된다.
- 일반
- 순차
서비스처럼 시작할 필요가 없다.
<receiver>
<intent-filter>
<action android:name="android.provider.Telephony.SMS_RECEIVED"/>
</intent-filter>
</receiver>
필터로 특정 액션정보만 받는다.
<uses-permission android:name="android.permission.RECEIVE_SMS"/>
권한을 한 후 사용자에게도 권한을 받아야 한다.
targetSdkVersion 이 22 이하이면 사용자권한이 필요없다.
가상 에뮬레이터로 메시지 받는방법
실험
- 앱이 꺼진 백그라운드에서도 실행됨
- targetSdkVersion 22 이하에서만 실행됨 (앱 재설치 필요)
- 브로드캐스트 리시버 생성
public class SmsReceiver extends BroadcastReceiver {
private static final String TAG = "SmsReceiver";
@Override
public void onReceive(Context context, Intent intent) {
Log.d(TAG, "onReceiver() 호출됨.");
}
}
SMS Receiver
public class SmsReceiver extends BroadcastReceiver {
private static final String TAG = "SmsReceiver";
@Override
public void onReceive(Context context, Intent intent) {
Log.d(TAG, "onReceiver() 호출됨.");
Bundle bundle = intent.getExtras();
SmsMessage[] messages = parseSmsMessage(bundle);
if (messages.length > 0) {
String sender = messages[0].getOriginatingAddress();
Log.d(TAG, "sender: " + sender);
String contents = messages[0].getMessageBody().toString();
Log.d(TAG, "contents: " + contents);
Date receiverDate = new Date(messages[0].getTimestampMillis());
Log.d(TAG, "received dated: " + receiverDate);
}
}
private SmsMessage[] parseSmsMessage(Bundle bundle) {
Object[] objs = (Object[]) bundle.get("pdus");
SmsMessage[] messages = new SmsMessage[objs.length];
for (int i = 0; i < objs.length; i++) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
String format = bundle.getString("format");
messages[i] = SmsMessage.createFromPdu((byte[]) objs[i], format);
} else {
messages[i] = SmsMessage.createFromPdu((byte[]) objs[i]);
}
}
return messages;
}
}
sms receiver
public class SmsReceiver extends BroadcastReceiver {
private static final String TAG = "SmsReceiver";
private static SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm");
@Override
public void onReceive(Context context, Intent intent) {
Log.d(TAG, "onReceiver() 호출됨.");
Bundle bundle = intent.getExtras();
SmsMessage[] messages = parseSmsMessage(bundle);
if (messages.length > 0) {
String sender = messages[0].getOriginatingAddress();
Log.d(TAG, "sender: " + sender);
String contents = messages[0].getMessageBody().toString();
Log.d(TAG, "contents: " + contents);
Date receivedDate = new Date(messages[0].getTimestampMillis());
Log.d(TAG, "received dated: " + receivedDate);
sendToActivity(context, sender, contents, receivedDate);
}
}
private void sendToActivity(Context context, String sender, String contents, Date receivedDate) {
Intent intent = new Intent(context, SmsActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK
|Intent.FLAG_ACTIVITY_SINGLE_TOP
|Intent.FLAG_ACTIVITY_CLEAR_TOP);
intent.putExtra("sender", sender);
intent.putExtra("contents", contents);
intent.putExtra("receivedDate", format.format(receivedDate));
context.startActivity(intent);
}
private SmsMessage[] parseSmsMessage(Bundle bundle) {
Object[] objs = (Object[]) bundle.get("pdus");
SmsMessage[] messages = new SmsMessage[objs.length];
for (int i = 0; i < objs.length; i++) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
String format = bundle.getString("format");
messages[i] = SmsMessage.createFromPdu((byte[]) objs[i], format);
} else {
messages[i] = SmsMessage.createFromPdu((byte[]) objs[i]);
}
}
return messages;
}
}
sms activity
public class SmsActivity extends AppCompatActivity {
private EditText editText;
private EditText editText2;
private EditText editText3;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_sms);
editText = findViewById(R.id.editText);
editText2 = findViewById(R.id.editText2);
editText3 = findViewById(R.id.editText3);
Button button = findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
finish();
}
});
Intent passedIntent = getIntent();
processCommand(passedIntent);
}
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
processCommand(intent);
}
private void processCommand(Intent intent) {
if (intent != null) {
String sender = intent.getStringExtra("sender");
String contents = intent.getStringExtra("contents");
String receivedDate = intent.getStringExtra("receivedDate");
editText.setText((sender));
editText3.setText((contents));
editText2.setText((receivedDate));
}
}
}
권한
위험권한 부여
일반권한과 위험권한 (마시멜로 API 23 부터)
- 위험권한은 실행 시 권한부여
대표 위험권한
- 위치
- 카메라
- 마이크
- 연락처
- 전화
- 문자
- 일정
- 센서
권한 대화상자
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button button2 = findViewById(R.id.button2);
int permissionCheck = ContextCompat.checkSelfPermission(this, Manifest.permission.RECEIVE_SMS);
if (permissionCheck == PackageManager.PERMISSION_GRANTED) {
Toast.makeText(this, "SMS 수신권한 주어져있음", Toast.LENGTH_LONG).show();
} else {
Toast.makeText(this, "SMS 수신권한 없음", Toast.LENGTH_LONG).show();
ActivityCompat.requestPermissions(this, new String[] {Manifest.permission.RECEIVE_SMS}, 1);
}
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
switch (requestCode) {
case 1:
if (grantResults.length > 0) {
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
Toast.makeText(this, "SMS 수신권한을 사용자가 승인함", Toast.LENGTH_LONG).show();
} else if (grantResults[0] == PackageManager.PERMISSION_DENIED){
Toast.makeText(this, "SMS 수신권한을 사용자가 거부함", Toast.LENGTH_LONG).show();
}
} else {
Toast.makeText(this, "SMS 수신권한을 부여받지 못함", Toast.LENGTH_LONG).show();
}
}
}
}
화면이동 정리
startActivityForresult() 로 requestCode 와 함께 데이터를 넘겨주면
화면이동 후 getIntent 로 받는다.
돌아갈 때 setResult() 와 requestCode 로 받는다.
- 데이터를 넘겨주지 않을 때
Intent intent = new Intent(getApplicationContext(), CommentWriteActivity.class);
startActivity();
- 데이터를 넘겨줄 때
Intent intent = new Intent(getApplicationContext(), CommentWriteActivity.class);
intent.putExtra("rating", rating);
startActivityForResult(intent, 101);
- 데이터를 받을 때
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_comment_write);
ratingBar = findViewById(R.id.ratingBar);
Intent intent = getIntent();
processIntent(intent);
}
private void processIntent(Intent intent) {
if (intent != null) {
float rating = intent.getFloatExtra("rating", 0.0f);
ratingBar.setRating(rating);
}
}
Main
public class MainActivity extends AppCompatActivity {
private RatingBar ratingBar;
TextView outputView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ratingBar = findViewById(R.id.ratingBar);
outputView = findViewById(R.id.outputView);
Button button = findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
showCommentWriteActivity();
}
});
}
private void showCommentWriteActivity() {
float rating = ratingBar.getRating();
Intent intent = new Intent(getApplicationContext(), CommentWriteActivity.class);
intent.putExtra("rating", rating);
startActivityForResult(intent, 101);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == 101) {
if (data != null) {
String contents = data.getStringExtra("contents");
outputView.setText(contents);
}
}
}
}
Comment
public class CommentWriteActivity extends AppCompatActivity {
private RatingBar ratingBar;
private EditText contentsInput;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_comment_write);
ratingBar = findViewById(R.id.ratingBar);
Intent intent = getIntent();
processIntent(intent);
contentsInput = findViewById(R.id.contentsInput);
Button saveButton = findViewById(R.id.saveButton);
saveButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
returnToMain();
}
});
}
private void returnToMain() {
String contents = contentsInput.getText().toString();
// 화면이동이 없다면 Intent 에 파라미터가 필요없다.
Intent intent = new Intent();
intent.putExtra("contents", contents);
setResult(RESULT_OK, intent);
finish();
}
private void processIntent(Intent intent) {
if (intent != null) {
float rating = intent.getFloatExtra("rating", 0.0f);
ratingBar.setRating(rating);
}
}
}
4. 화면 내비게이션
프래그먼트
액티비티와 사용방법이 비슷하다.
프래그먼트도 액티비티처럼 화면전환으로 사용할 수 있다.
setContentView 는 액티비티에만 있어서 프래그먼트는 인플레이션을 직접 해주어야 한다.
Fragment
상속받는다.
fragment
public class fragmentActivity extends Fragment {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.activity_fragment, container, false);
return rootView;
}
}
activity_main.xml
<fragment
android:layout_width="match_parent"
android:layout_height="match_parent"
android:name="com.example.a17myfragment2.ListFragment"
android:id="@+id/listFragment"/>
xml 에 선언한 경우 자바에서는
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
FragmentManager manager = getSupportFragmentManager();
fragment1 = (ListFragment) manager.findFragmentById(R.id.listFragment);
fragment2 = (ViewerFragment) manager.findFragmentById(R.id.viewerFragment);
}
findFragmentById 를 사용해 변수로 가져온다.
xml 에 추가해도 되지만 java 에 추가해도 된다.
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
fragmentActivity fragment1 = new fragmentActivity();
getSupportFragmentManager().beginTransaction().add(R.id.container, fragment1).commit();
}
});
프래그먼트로 화면이동
public class MainActivity extends AppCompatActivity {
fragmentActivity fragment1;
MenuFragment fragment2;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
fragment1 = new fragmentActivity();
fragment2 = new MenuFragment();
Button main = findViewById(R.id.main);
main.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
getSupportFragmentManager().beginTransaction().replace(R.id.container, fragment1).commit();
}
});
Button menu = findViewById(R.id.menu);
menu.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
getSupportFragmentManager().beginTransaction().replace(R.id.container, fragment2).commit();
}
});
}
}
프래그먼트 내 버튼
@Override
public void onAttach(Context context) {
super.onAttach(context);
activity = (MainActivity) getActivity();
}
@Override
public void onDetach() {
super.onDetach();
activity = null;
}
프래그먼트 내 버튼으로 이동 시 onAttach 와 onDetach 를 오버라이딩 해야합니다.
화면이동 메서드
public void onFragmentChange(int index) {
if (index == 0) {
getSupportFragmentManager().beginTransaction().replace(R.id.container, fragment1).commit();
} else {
getSupportFragmentManager().beginTransaction().replace(R.id.container, fragment2).commit();
}
}
화면이동을 메서드로 정의해도 됩니다.
프래그먼트 활성화 (액티비티 화면에 보이는 상태)
프래그먼트 이미지변환
Main
public class MainActivity extends AppCompatActivity {
ListFragment fragment1;
ViewerFragment fragment2;
FragmentManager manager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
manager = getSupportFragmentManager();
fragment1 = (ListFragment) manager.findFragmentById(R.id.listFragment);
fragment2 = (ViewerFragment) manager.findFragmentById(R.id.viewerFragment);
}
public void onImageChange(int index) {
fragment2.setImage(index);
}
}
ListFragment
public class ListFragment extends Fragment {
private MainActivity activity;
@Override
public void onDetach() {
super.onDetach();
activity = null;
}
@Override
public void onAttach( Context context) {
super.onAttach(context);
activity = (MainActivity) getActivity();
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_list, container, false);
Button button = rootView.findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
activity.onImageChange(0);
}
});
Button button2 = rootView.findViewById(R.id.button2);
button2.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
activity.onImageChange(1);
}
});
Button button3 = rootView.findViewById(R.id.button3);
button3.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
activity.onImageChange(2);
}
});
return rootView;
}
}
ViewerFragment
public class ViewerFragment extends Fragment {
private ImageView imageView;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_viewer, container, false);
imageView = rootView.findViewById(R.id.imageView);
return rootView;
}
public void setImage(int index) {
if (index == 0) {
imageView.setImageResource(R.mipmap.ic_launcher);
} else if (index == 1) {
imageView.setImageResource(R.drawable.ic_launcher_foreground);
} else {
imageView.setImageResource(R.drawable.ic_launcher_background);
}
}
}
액션바
res 에 menu 폴더를 만든다.
xmlns:android="http://schemas.android.com/apk/res/android"
안드로이드에 정해진 속성을 쓰겠다.
xmlns:app="http://schemas.android.com/apk/res-auto"
앱에 정해진 속성을 쓰겠다.
버튼구현
menu
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item android:id="@+id/menu_refresh"
android:title="새로고침"
android:icon="@drawable/menu_refresh"
app:showAsAction="always"/>
<item android:id="@+id/menu_search"
android:title="검색"
android:icon="@drawable/menu_search"
app:showAsAction="always"/>
<item android:id="@+id/menu_settings"
android:title="설정"
android:icon="@drawable/menu_settings"
app:showAsAction="always"/>
</menu>
MainActivity.java
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.menu_main, menu);
return true;
}
call back method 란?
어떤 상황이 됐을 때 호출되는 함수
버튼이벤트
@Override
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
int curId = item.getItemId();
switch(curId) {
case R.id.menu_refresh:
Toast.makeText(this, "새로고침 메뉴 클릭됨", Toast.LENGTH_SHORT).show();
break;
case R.id.menu_search:
Toast.makeText(this, "검색 메뉴 클릭됨", Toast.LENGTH_SHORT).show();
break;
case R.id.menu_settings:
Toast.makeText(this, "설정 메뉴 클릭됨", Toast.LENGTH_SHORT).show();
break;
default:
break;
}
return super.onOptionsItemSelected(item);
}
액션바 없애기
themes 폴더 내 themes.xml 파일
<style name="Theme.MyOptionMenu" parent="Theme.MaterialComponents.DayNight.NoActionBar">
parent 에서 NoActionBar 로 바꾸면 액션바가 없어진다.
혹은
MainActivity.java
ActionBar abar = getSupportActionBar();
abar.hide();
를 onCreate 에 선언해도 된다.
액션바에 레이아웃넣기
<item android:id="@+id/menu_search"
android:title="검색"
android:icon="@drawable/menu_search"
app:showAsAction="always|withText"
app:actionLayout="@layout/activity_search"/>
툴바는 다음과 같은 것을 임폴트해야 한다.
import androidx.appcompat.widget.Toolbar;
탭 만들기
프로젝트 스트럭처 - 디펜던시 - com.android.support.design 추가build.gradle 에서implementation 'com.android.support:design:30.+' 와 같이현재버전과 맞춰준다.~
activity_main.xml
<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.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:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/design_default_color_primary"
android:theme="@style/ThemeOverlay.AppCompat.Dark"
android:elevation="1dp"
android:id="@+id/toolbar">
</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:elevation="1dp"
android:background="@android:color/background_light"
app:tabMode="fixed"
app:tabGravity="fill"
app:tabTextColor="@color/design_default_color_primary"
app:tabSelectedTextColor="@color/design_default_color_primary">
</com.google.android.material.tabs.TabLayout>
</com.google.android.material.appbar.AppBarLayout>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior"
android:id="@+id/container">
</FrameLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
MainActivity.java
public class MainActivity extends AppCompatActivity {
FirstFragment fragment1;
SecondFragment fragment2;
ThirdFragment fragment3;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
fragment1 = new FirstFragment();
fragment2 = new SecondFragment();
fragment3 = new ThirdFragment();
getSupportFragmentManager().beginTransaction().add(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.setOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
@Override
public void onTabSelected(TabLayout.Tab tab) {
int position = tab.getPosition();
Fragment selected = null;
switch (position) {
case 0:
selected = fragment1;
break;
case 1:
selected = fragment2;
break;
case 2:
selected = fragment3;
break;
default:
break;
}
getSupportFragmentManager().beginTransaction().replace(R.id.container, selected).commit();
}
@Override
public void onTabUnselected(TabLayout.Tab tab) {
}
@Override
public void onTabReselected(TabLayout.Tab tab) {
}
});
}
}
*강사쌤은 if 문으로 작성했지만 switch 문이 더 직관적이라서 수정했습니다.
뷰페이저
어댑터를 통해 만든다.
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ViewPager pager = findViewById(R.id.pager);
pager.setOffscreenPageLimit(3);
MoviePagerAdapter adapter = new MoviePagerAdapter(getSupportFragmentManager());
FirstFragment fragment1 = new FirstFragment();
SecondFragment fragment2 = new SecondFragment();
ThirdFragment fragment3 = new ThirdFragment();
adapter.addItem(fragment1);
adapter.addItem(fragment2);
adapter.addItem(fragment3);
pager.setAdapter(adapter);
}
class MoviePagerAdapter extends FragmentStatePagerAdapter {
ArrayList<Fragment> items = new ArrayList<>();
public MoviePagerAdapter(@NonNull @NotNull FragmentManager fm) {
super(fm);
}
public void addItem(Fragment item) {
items.add(item);
}
@NonNull
@NotNull
@Override
public Fragment getItem(int position) {
return items.get(position);
}
@Override
public int getCount() {
return items.size();
}
}
}
유지보수
adapter.addItem(new ThirdFragment());
하나의 프래그먼트와 한 줄의 코드를 통해 화면을 추가할 수 있다.
타이틀 스트립
뷰페이저를 넘기면 어느 화면을 나타내는지 알 수 없다.
탭스트립 혹은 타이틀스트립이 그것을 보여준다.
activity_main.xml
<androidx.viewpager.widget.PagerTitleStrip
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:foregroundGravity="top"
android:background="#55cedf"
android:paddingTop="5dp"
android:paddingBottom="5dp">
</androidx.viewpager.widget.PagerTitleStrip>
MainActivity.java
@Nullable
@org.jetbrains.annotations.Nullable
@Override
public CharSequence getPageTitle(int position) {
return "페이지 " + position;
}
버튼으로 뷰페이저 보기: MainActivity.java
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
pager.setCurrentItem(1);
}
});
바로가기 메뉴
drawer layout: 다른 레이아웃을 담을 수 있다.
<include>
다른 레이아웃을 넣는다.
onOptionsItemSelected
옵션선택 오버라이딩 메서드
버튼으로 뷰페이저 작동
추가한 코드
MainActivity.java 내 onCreate
fragment1 = new FirstFragment();
fragment2 = new SecondFragment();
fragment3 = new ThirdFragment();
toolbar = findViewById(R.id.toolbar);
toolbar.setTitle("첫번째 화면");
getSupportFragmentManager().beginTransaction().add(R.id.container, fragment1).commit();
navigationView.setNavigationItemSelectedListener(new NavigationView.OnNavigationItemSelectedListener() {
@Override
public boolean onNavigationItemSelected(@NonNull @NotNull MenuItem item) {
switch (item.getItemId()) {
case R.id.nav_home:
Toast.makeText(MainActivity.this, "첫 번째 메뉴", Toast.LENGTH_SHORT).show();
onFragmentSelected(0, null);
drawer.close();
break;
case R.id.nav_gallery:
Toast.makeText(MainActivity.this, "두 번째 메뉴", Toast.LENGTH_SHORT).show();
onFragmentSelected(1, null);
drawer.close();
break;
case R.id.nav_slideshow:
Toast.makeText(MainActivity.this, "세 번째 메뉴", Toast.LENGTH_SHORT).show();
onFragmentSelected(2, null);
drawer.close();
break;
default:
break;
}
return true;
}
});
새 인터페이스
public interface FragmentCallback {
public void onFragmentSelected(int position, Bundle bundle);
}
MainActivity.java 내 메서드
직접 구현한 인터페이스를 상속받았습니다.
@Override
public void onFragmentSelected(int position, Bundle bundle) {
Fragment curFragment = null;
switch (position) {
case 0:
curFragment = fragment1;
toolbar.setTitle("첫번째 화면");
break;
case 1:
curFragment = fragment2;
toolbar.setTitle("두번째 화면");
break;
case 2:
curFragment = fragment3;
toolbar.setTitle("세번째 화면");
break;
default:
break;
}
getSupportFragmentManager().beginTransaction().replace(R.id.container, curFragment).commit();
}
정리
프래그먼트 띄우기
getSupportFragmentManager().beginTransaction().add(R.id.container, fragment1).commit();
- sfm . . a R.id.container, f c
Q. onAttach 에서 Context 는 어떤 것일까?
A.
데이터 주고받기: 프래그먼트
프래그먼트에서 메인으로 데이터넘기기
FirstFragment.java
public class F1Fragment extends Fragment {
FragmentCallback callback;
@Override
public void onAttach(@NonNull @NotNull Context context) {
super.onAttach(context);
if (context instanceof FragmentCallback) {
callback = (FragmentCallback) context;
}
}
@Override
public void onDetach() {
super.onDetach();
if (callback != null) {
callback = null;
}
}
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
View root = inflater.inflate(R.layout.f1_fragment, container, false);
Button button = root.findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (callback != null) {
callback.onCommand("show", "프래그먼트1에서 전달함");
}
}
});
return root;
}
}
새 인터페이스
public interface FragmentCallback {
public void onCommand(String command, String data);
}
MainActivity.java 내 메서드
@Override
public void onCommand(String command, String data) {
button2.setText(data);
}
데이터를 넘길 때 프래그먼트에서 메인으로는 인터페이스가 필요하고 메인에서 프래그먼트로는 인터페이스가 필요없다.
메인에서 프래그먼트로 데이터넘기기
MainActivity.java 내 onCreate
button2.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (fragment1 != null) {
fragment1.onCommandFromActivity("show", "액티비티로부터 전달됨.");
}
}
});
FirstFragment.java 내 메서드
public void onCommandFromActivity(String command, String data) {
textView.setText(data);
}
5. 네트워킹
스레드
자바의 스레드를 기본으로 쓸 수 있다.
- 스레드 만들기
- 화면에 보여주기
- 애니메이션 만들기
- 트윈 애니메이션 만들기
- 그래프 애니메이션 만들기
동시에 리소스를 접근할 경우 데드락 (DeadLcok) 이 발생
표준자바에서 스레드 사용방법
- 스레드는 new 연산자를 이용하여 객체를 생성한 후 start() 메서드를 호출하면 시작함
- Thread 클래스에 정의된 생성자는 그게 파라미터가 없는경우와 Runnable 객체를 파라미터로 가지는 두가지로 구분함
running = true;
Thread thread1 = new BackgroundThread();
thread1.start();
핸들러란? (Handler)
- 메시지 큐를 이용해 메인스레드에서 처리할 메시지를 다른 스레드로 전달하는 역할을 담당함
- 특정 메시지가 미래의 어떤시점에 실행되도록 스케줄링 할 수 있음
obtainMessage()
sendMessage()
handleMessage()
스레드 예제
제일 아래의 압축형코드를 쓰시기를 추천합니다.
TextView textView = (TextView) findViewById(R.id.textView);
Button start = (Button) findViewById(R.id.start);
start.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
BackgroundThread thread = new BackgroundThread();
thread.start();
}
});
Button check = (Button) findViewById(R.id.check);
check.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
textView.setText("현재 값: " + value);
}
});
class BackgroundThread extends Thread {
boolean running = false;
public void run() {
running = true;
while (running) {
value += 1;
try {
Thread.sleep(1000);
} catch (Exception e) {
}
}
}
}
값 입력
- 핸들러선언
- 메시지선언
- 번들선언
bundle.putInt("key", value)
번들에 값을 저장합니다.msg.setData(bundle)
메시지에 담습니다.handler.sendMessage(msg)
핸들러로 메시지를 보냅니다.
값 읽기
msg.getData().getInt("key")
key 값과 연결된 value 를 int 형으로 반환합니다.
msg.getData()
bundle 값을 반환합니다.
멀티스레드 예제
textView = (TextView) findViewById(R.id.textView);
Button start = (Button) findViewById(R.id.start);
start.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
BackgroundThread thread = new BackgroundThread();
thread.start();
}
});
class BackgroundThread extends Thread {
int value = 0;
boolean running = false;
public void run() {
running = true;
while (running) {
value += 1;
Message msg = handler.obtainMessage();
Bundle bundle = new Bundle();
bundle.putInt("key", value);
msg.setData(bundle);
handler.sendMessage(msg);
try {
Thread.sleep(1000);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
class ValueHandler extends Handler {
@Override
public void handleMessage(@NonNull Message msg) {
super.handleMessage(msg);
int value = msg.getData().getInt("key");
textView.setText("현재 값: " + value);
}
}
date: 07.19
압축형 코드
start.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
int value= i;
handler.post(new Runnable() {
@Override
public void run() {
textView.setText("현재 값: " + value);
}
});
try {
Thread.sleep(1000);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}).start();
}
});
사용방법
- 뉴 쓰레드 (뉴 러너블)
- 런 메서드 내부에 핸들러 포스트 (뉴 러너블)
- 쓰레드 스타트
start.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
new Thread(new Runnable() {
@Override
public void run() {
handler.post(new Runnable() {
@Override
public void run() {
}
});
}
}).start();
}
});
AsyncTask
필수요소: doInBackground (콜백요소)
선택요소: onPostExecute, onProgressUpdate
doInBackground 에서 PostExecute 로 값을 넘겨준다.
publishProgress 를 선언하면 onProgressUpdate 를 호출
프로그래스 바 넣기: MainActivity.java
public class MainActivity extends AppCompatActivity {
ProgressBar progressBar;
Handler handler = new Handler();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button button = findViewById(R.id.button);
progressBar = findViewById(R.id.progressBar);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
ProgressThread thread = new ProgressThread();
thread.start();
}
});
}
class ProgressThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
int value = i;
handler.post(new Runnable() {
@Override
public void run() {
progressBar.setProgress(value);
}
});
try {
Thread.sleep(200);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}
handler
.post
.postAtFrontOfQueue
.postAtTime
.postDelayed
postDelayed 예시
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
handler.postDelayed(new Runnable() {
@Override
public void run() {
ProgressThread thread = new ProgressThread();
thread.start();
}
}, 5000);
}
});
onPostExecute
실행이 끝나면 메서드 실행
AsyncTask 예제
public class MainActivity extends AppCompatActivity {
ProgressBar progressBar;
@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 v) {
ProgressTask task = new ProgressTask();
task.execute("시작");
}
});
}
class ProgressTask extends AsyncTask<String, Integer, Integer> {
@Override
protected Integer doInBackground(String... strings) {
int value;
for (value = 0; value < 100; value++) {
publishProgress(value);
try {
Thread.sleep(200);
} catch (Exception e) {
e.printStackTrace();
}
}
return value;
}
@Override
protected void onProgressUpdate(Integer... values) {
super.onProgressUpdate(values);
progressBar.setProgress(values[0].intValue());
}
@Override
protected void onPostExecute(Integer integer) {
super.onPostExecute(integer);
Toast.makeText(MainActivity.this, "완료됨", Toast.LENGTH_SHORT).show();
}
}
}
스레드보다 AsyncTask 를 이용하는 경우가 많다.
소켓&HTTP
웹서버에서 데이터를 가져오는 것을 구현한다.
일반적인 프로그래밍에서는 대부분 TCP 연결을 사용한다.
주의할 점
- 네트워킹을 사용할 때에는 반드시 스레드 사용
- UI 업데이트를 위해 반드시 핸들러 사용. post() 메서드사용 권장
Manifest.xml
<uses-permission android:name="android.permission.INTERNET"/>
서버 스레드
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 v) {
ServerThread thread = new ServerThread();
thread.start();
}
});
}
class ServerThread extends Thread {
@Override
public void run() {
int port = 5001;
try {
ServerSocket server = new ServerSocket(port);
Log.d("ServerThread", "서버가 실행됨");
while (true) {
Socket socket = server.accept();
ObjectInputStream instream = new ObjectInputStream(socket.getInputStream());
Object input = instream.readObject();
Log.d("ServerThread", "input:" + input);
ObjectOutputStream outstream = new ObjectOutputStream(socket.getOutputStream());
outstream.writeObject(input + " from server.");
outstream.flush();
Log.d("ServerThread", "output 보냄");
socket.close();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
소켓 스레드
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 v) {
ClientThread thread = new ClientThread();
thread.start();
}
});
}
class ClientThread extends Thread {
@Override
public void run() {
String host = "localhost";
int port = 5001;
try {
Socket socket = new Socket(host, port);
ObjectOutputStream outstream = new ObjectOutputStream(socket.getOutputStream());
outstream.writeObject("안녕!");
outstream.flush();
Log.d("ClentThread", "서버로 보냄");
ObjectInputStream instream = new ObjectInputStream(socket.getInputStream());
Object input = instream.readObject();
Log.d("ClientThread", "받은 데이터: " + input);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
UI 에 넘겨주려면 꼭 handler 를 써야한다.
핸들러: MySocket - MainActivity.java
handler.post(new Runnable() {
@Override
public void run() {
textView.setText("받은데이터: " + input);
}
});
서비스 실행: MyServer - MainActivity.java
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(getApplicationContext(), ChatService.class);
startService(intent);
}
});
챗 서비스: MyServer - 새 서비스
public class ChatService extends Service {
@Override
public void onCreate() {
super.onCreate();
ServerThread thread = new ServerThread();
thread.start();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
return super.onStartCommand(intent, flags, startId);
}
@Override
public void onDestroy() {
super.onDestroy();
}
public ChatService() {
}
@Override
public IBinder onBind(Intent intent) {
// TODO: Return the communication channel to the service.
throw new UnsupportedOperationException("Not yet implemented");
}
class ServerThread extends Thread {
@Override
public void run() {
int port = 5001;
try {
ServerSocket server = new ServerSocket(port);
Log.d("ServerThread", "서버가 실행됨");
while (true) {
Socket socket = server.accept();
ObjectInputStream instream = new ObjectInputStream(socket.getInputStream());
Object input = instream.readObject();
Log.d("ServerThread", "input:" + input);
ObjectOutputStream outstream = new ObjectOutputStream(socket.getOutputStream());
outstream.writeObject(input + " from server.");
outstream.flush();
Log.d("ServerThread", "output 보냄");
socket.close();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
HTTP
HTTP 는 소켓과 별반 다르지 않다.
요청
응답
요청코드를 작성하는 것이 복잡하므로 volley 라는 라이브러리를 사용하여 작성한다.
웹으로 요청
- 자바에서 사용하는 클래스를 그대로 사용할 수 있다.
URL Connection 클래스
Manifest
android:usesCleartextTraffic="true"
HTTP 예시
public class MainActivity extends AppCompatActivity {
EditText editText;
TextView textView;
Handler handler = new Handler();
String urlStr;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
textView = findViewById(R.id.textView);
editText = findViewById(R.id.editText);
Button button = findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
urlStr = editText.getText().toString();
RequestThread thread = new RequestThread();
thread.start();
}
});
}
class RequestThread extends Thread {
@Override
public void run() {
try {
URL url = new URL(urlStr);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
if (conn != null) {
conn.setConnectTimeout(10000);
conn.setRequestMethod("GET");
conn.setDoInput(true);
conn.setDoOutput(true);
int resCode = conn.getResponseCode();
BufferedReader reader = new BufferedReader(new InputStreamReader(conn.getInputStream()));
String line = null;
while (true) {
line = reader.readLine();
if (line == null) {
break;
}
println(line);
}
reader.close();
conn.disconnect();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
public void println(String data) {
handler.post(new Runnable() {
@Override
public void run() {
textView.append(data + "\n");
}
});
}
}
Volley&GSON
코드의 양이 적고 스레드를 사용하지 않는다.
- Request 객체를 만든다.
- RequestQueue 에 추가해준다: 자동으로 스레드를 만들어준다.
볼리 라이브러리
implementation 'com.android.volley:volley:1.1.0'
영화 API: https://www.kobis.or.kr/kobisopenapi/homepg/main/main.do{:target="_blank"}
볼리 예제
Mainfest
<uses-permission android:name="android.permission.INTERNET"/>
<application
android:usesCleartextTraffic="true">
MainActivity.java
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);
Button button = findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
sendRequest();
}
});
if (AppHelper.requestQueue == null) {
AppHelper.requestQueue = Volley.newRequestQueue(getApplicationContext());
}
}
public void sendRequest() {
String url = "http://www.google.co.kr";
StringRequest request = new StringRequest(
Request.Method.GET,
url,
new Response.Listener<String>() {
@Override
public void onResponse(String response) {
println("응답 -> " + response);
}
},
new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
println("에러 -> " + error.getMessage());
}
}
) {
@Override
protected Map<String, String> getParams() throws AuthFailureError {
Map<String, String> params = new HashMap<>();
return params;
}
};
request.setShouldCache(false);
AppHelper.requestQueue.add(request);
println("요청 보냄.");
}
public void println(String data) {
textView.append(data + "\n");
}
}
AppHelper
public class AppHelper {
public static RequestQueue requestQueue;
}
JSON
데이터포맷이다.
자바스크립트 객체로 바꿔 바로 사용가능하다.
구글에서 만든 GSON 은 이 객체를 자바객체로 바꿔준다.
GSON
implementation 'com.google.code.gson:gson:2.8.2'
예시 JSON 파일: http://kobis.or.kr/kobisopenapi/webservice/rest/boxoffice/searchDailyBoxOfficeList.json?key=f5eef3421c602c6cb7ea224104795888&targetDt=20120101{:target="_blank"}
{ }
중괄호: 객체
[ ]
대괄호: 배열
JSON 정렬 사이트: http://json.parser.online.fr/{:target="_blank"}
{
"boxOfficeResult":{
"boxofficeType":"일별 박스오피스",
"showRange":"20120101~20120101",
"dailyBoxOfficeList":[10]
}
}
클래스로 구조를 만든다.
- 가장 상단 클래스 생성
- boxOfficeResult 클래스 생성
- DailyBoxOfficeList 클래스 생성
최상위 클래스
public class MovieList {
MovieListResult boxOfficeResult;
}
boxOfficeResult
public class MovieListResult {
String boxofficeType;
String showRange;
ArrayList<Movie> dailyBoxOfficeList = new ArrayList<>();
}
DailyBoxOfficeList
public class Movie {
String rnum;
String rank;
String rankInten;
String rankOldAndNew;
String movieCd;
String movieNm;
String openDt;
String salesAmt;
String salesShare;
String salesInten;
String salesChange;
String salesAcc;
String audiCnt;
String audiInten;
String audiChange;
String audiAcc;
String scrnCnt;
String showCnt;
}
GSON 의 사용방법
- processResponse 메서드를 작성
public void processResponse(String response) {
Gson gson = new Gson();
MovieList movieList = gson.fromJson(response, MovieList.class);
if (movieList != null) {
int countMovie = movieList.boxOfficeResult.dailyBoxOfficeList.size();
println("박스오피스 타입: " + movieList.boxOfficeResult.boxofficeType);
println("응답받은 영화갯수: " + countMovie);
}
}
processResponse(response)
Response.Listener 내부에 선언
전체코드
StringRequest request = new StringRequest(
Request.Method.GET,
url,
new Response.Listener<String>() {
@Override
public void onResponse(String response) {
println("응답 -> " + response);
processResponse(response);
}
},
new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
println("에러 -> " + error.getMessage());
}
}
) {
@Override
protected Map<String, String> getParams() throws AuthFailureError {
Map<String, String> params = new HashMap<>();
return params;
}
};
이미지 다운로드
onPreExecute - doInBackground - onProgressUpdate - onPostExecute 순으로 실행된다.
ImageLoadTask.java
public class ImageLoadTask extends AsyncTask<Void, Void, Bitmap> {
private String urlStr;
private ImageView imageView;
public ImageLoadTask(String urlStr, ImageView imageView) {
this.urlStr = urlStr;
this.imageView = imageView;
}
@Override
protected void onPreExecute() {
super.onPreExecute();
}
@Override
protected Bitmap doInBackground(Void... voids) {
Bitmap bitmap = null;
try {
URL url = new URL(urlStr);
bitmap = BitmapFactory.decodeStream(url.openConnection().getInputStream());
} catch (Exception e) {
e.printStackTrace();
}
return bitmap;
}
@Override
protected void onProgressUpdate(Void... values) {
super.onProgressUpdate(values);
}
@Override
protected void onPostExecute(Bitmap bitmap) {
super.onPostExecute(bitmap);
imageView.setImageBitmap(bitmap);
imageView.invalidate();
}
}
메인메서드
private void sendImageRequest() {
String url = "https://movie-phinf.pstatic.net/20210610_86/1623290294202tkWYJ_JPEG/movie_image.jpg?type=m665_443_2";
ImageLoadTask task = new ImageLoadTask(url, imageView);
task.execute();
}
메모리
@Override
protected Bitmap doInBackground(Void... voids) {
Bitmap bitmap = null;
try {
if (bitmapHash.containsKey(urlStr)) {
Bitmap oldBitmap = bitmapHash.remove(urlStr);
if (oldBitmap != null) {
oldBitmap.recycle();
oldBitmap = null;
}
}
URL url = new URL(urlStr);
bitmap = BitmapFactory.decodeStream(url.openConnection().getInputStream());
bitmapHash.put(urlStr, bitmap);
} catch (Exception e) {
e.printStackTrace();
}
return bitmap;
}
보통은 HashMap 을 사용하지 않고 데이터베이스에 캐쉬로 저장했다가 비트맵이 이미 있다면 거기서 불러온다.
프로그레스바를 넣어 이미지요청에 대해 visible, unvisible 을 설정할 수 있다.
영화 API
샘플 영화 API: http://boostcourse-appapi.connect.or.kr:10000/movie/readMovieList?type=1{:target="_blank"}
JSON View or Viewer 를 브라우저 플러그인 (확장프로그램) 을 받으면 브라우저에서 정렬된 JSON 코드를 볼 수 있다.
정리
Volley 내부에 Gson 을 선언한다.
Mainfest
<uses-permission android:name="android.permission.INTERNET"/>
<application
android:usesCleartextTraffic="true">
build.gradle
implementation 'com.android.volley:volley:1.1.0'
implementation 'com.google.code.gson:gson:2.8.2'
data 패키지 내부에 클래스 생성
public class ResponseInfo {
public String message;
public int code;
public String resultType;
}
public class MovieList {
public ArrayList<MovieInfo> result = new ArrayList<>();
}
public class MovieInfo {
public int id;
public String title;
public String title_eng;
public String date;
public float user_rating;
public float audience_rating;
public float reviewer_rating;
public float reservation_rate;
public int reservation_grade;
public int grade;
public String thumb;
public String image;
}
Volley
볼리 라이브러리를 사용해 스레드와 핸들러를 사용할 필요가 없어진다.
public class AppHelper {
public static RequestQueue requestQueue;
public static String host = "boostcourse-appapi.connect.or.kr";
public static int port = 10000;
}
MainActivity.java
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);
Button button = findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
requestMovieList();
}
});
if (AppHelper.requestQueue == null) {
AppHelper.requestQueue = Volley.newRequestQueue(getApplicationContext());
}
}
public void requestMovieList() {
String url = "http://" + AppHelper.host + ":" + AppHelper.port + "/movie/readMovieList";
url += "?" + "type=1";
StringRequest request = new StringRequest(
Request.Method.GET,
url,
new Response.Listener<String>() {
@Override
public void onResponse(String response) {
println("응답받음 -> " + response);
processResponse(response);
}
},
new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
println("에러발생 -> " + error.getMessage());
}
}
);
request.setShouldCache(false);
AppHelper.requestQueue.add(request);
println("영화목록 요청보냄.");
}
public void processResponse(String response) {
Gson gson = new Gson();
ResponseInfo info = gson.fromJson(response, ResponseInfo.class);
if (info.code == 200) {
MovieList movieList = gson.fromJson(response, MovieList.class);
println("영화갯수: " + movieList.result.size());
for (int i = 0; i < movieList.result.size(); i++) {
MovieInfo movieInfo = movieList.result.get(i);
println("영화 #" + i + " -> " + movieInfo.id + ", " + movieInfo.title + ", " + movieInfo.grade);
}
}
}
public void println(String data) {
textView.append(data + "\n");
}
}
6. 데이터베이스
이론
테이블을 Relation 이라고 한다.
행을 Tuple 이라고 한다.
열을 Column or Attribute 라고 한다.
테이블의 구조 (스키마) 를 만들어야 한다.
테이블 생성
DROP TABLE NAME
테이블 삭제
데이터 입력
데이터 수정
데이터 삭제
DELETE
데이터 조회
테이블
안드로이드 데이터저장 방법
- 설정정보
- 파일사용
- 데이터베이스
데이터베이스 순서
- 데이터베이스 만들기
- 테이블 만들기
- 레코드 추가하기
- 데이터 조회하기
데이터베이스를 만드는 간단한 방법
- Context 클래스에 정의된 openOrCreateDatabase() 메소드를 사용
- 기본적으로 사용하는 Activity 클래스가 Context 를 상속한 것이므로 액티비티 안에서 데이터베이스 생성 가능
SQLite 의 데이터를 PC 로도 볼 수 있다.
예시
데이터베이스 열기
public void openDatabase(String databaseName) {
println("openDatabase() 호출됨.");
database = openOrCreateDatabase(databaseName, MODE_PRIVATE, null);
if (database != null) {
println("데이터베이스 오픈됨.");
}
}
database 의 타입은 SQLiteDatabse 이다.
database.execSQL(sql)
결과값을 반환하지 않는 SQL 문은 execSQL 로 작성한다.
Q&A
Q. 자바에서 SQL 을 작성할 때 대문자로 작성하나요?
A. 기본제공 키워드는 대문자로 아니라면 소문자로 작성하는 것이 가독성에 좋습니다.
Q. 레이아웃에서 ems 속성이란?
A. layout_width 가 wrap_content 일 때 textSize 에 비례해 뷰의 사이즈가 커집니다.
Q. trim 메소드란?
A. 자바에서 trim 메소드란 문자열의 앞 뒤 공백을 제거한 문자열을 반환합니다.
데이터베이스에서 공백이 들어간 문자열이 입력되면 문제가 생길 수 있기 때문에 trim 메소드를 활용합니다.
테이블 만들기
public void createTable(String tableName) {
println("createTable() 호출됨.");
if (database != null) {
String sql = "create table " + tableName + "(_id integer PRIMARY KEY autoincrement, name text, age integer, mobile text)";
database.execSQL(sql);
println("테이블 생성됨.");
} else {
println("먼저 데이터베이스를 오픈하세요.");
}
}
데이터 추가
button3.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String name = editText3.getText().toString().trim();
String ageStr = editText4.getText().toString().trim();
String mobile = editText5.getText().toString().trim();
int age = -1;
try {
age = Integer.parseInt(ageStr);
} catch (Exception e) {
e.printStackTrace();
}
insertData(name, age, mobile);
}
});
public void insertData(String name, int age, String mobile) {
println("insertData() 호출됨.");
if (database != null) {
String sql = "insert into customer(name, age, mobile) values(?, ?, ?)";
Object[] params = {name, age, mobile};
database.execSQL(sql, params);
println("데이터 추가함.");
} else {
println("먼저 데이터베이스를 오픈하세요.");
}
}
데이터베이스 오픈은 매번 해야하고 테이블은 한 번 만들면 더 이상 만들 필요가 없다.
Cursor 는 Jquery 의 result set 과 비슷하다.
데이터 조회
public void selectData(String tableName) {
println("selectData() 호출됨.");
if (database != null) {
String sql = "select name, age, mobile from " + tableName;
Cursor cursor = database.rawQuery(sql, null); // null 에 ? 의 리스트를 넣을 수 있다.
println("조회된 데이터 개수: " + cursor.getCount()); // ex) Object[] params = {name, age, mobile}
for (int i = 0; i < cursor.getCount(); i++) {
cursor.moveToNext();
String name = cursor.getString(0);
int age = cursor.getInt(1);
String mobile = cursor.getString(2);
println("#" + i + " -> " + name + ", " + age + ", " + mobile);
}
cursor.close();
}
}
전체코드
public class MainActivity extends AppCompatActivity {
EditText editText;
TextView textView;
EditText editText2;
EditText editText3;
EditText editText4;
EditText editText5;
SQLiteDatabase database;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
editText = findViewById(R.id.editText);
textView = findViewById(R.id.textView);
editText2 = findViewById(R.id.editText2);
editText3 = findViewById(R.id.editText3);
editText4 = findViewById(R.id.editText4);
editText5 = findViewById(R.id.editText5);
Button button = findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String databaseName = editText.getText().toString();
openDatabase(databaseName);
}
});
Button button2 = findViewById(R.id.button2);
button2.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String tableName = editText2.getText().toString();
createTable(tableName);
}
});
Button button3 = findViewById(R.id.button3);
button3.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String name = editText3.getText().toString().trim();
String ageStr = editText4.getText().toString().trim();
String mobile = editText5.getText().toString().trim();
int age = -1;
try {
age = Integer.parseInt(ageStr);
} catch (Exception e) {
e.printStackTrace();
}
insertData(name, age, mobile);
}
});
Button button4 = findViewById(R.id.button4);
button4.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String tableName = editText2.getText().toString();
selectData(tableName);
}
});
}
public void selectData(String tableName) {
println("selectData() 호출됨.");
if (database != null) {
String sql = "select name, age, mobile from " + tableName;
Cursor cursor = database.rawQuery(sql, null);
println("조회된 데이터 개수: " + cursor.getCount());
for (int i = 0; i < cursor.getCount(); i++) {
cursor.moveToNext();
String name = cursor.getString(0);
int age = cursor.getInt(1);
String mobile = cursor.getString(2);
println("#" + i + " -> " + name + ", " + age + ", " + mobile);
}
cursor.close();
}
}
public void insertData(String name, int age, String mobile) {
println("insertData() 호출됨.");
if (database != null) {
String sql = "insert into customer(name, age, mobile) values(?, ?, ?)";
Object[] params = {name, age, mobile};
database.execSQL(sql, params);
println("데이터 추가함.");
} else {
println("먼저 데이터베이스를 오픈하세요.");
}
}
public void createTable(String tableName) {
println("createTable() 호출됨.");
if (database != null) {
String sql = "create table [if not exists] " + tableName + "(_id integer PRIMARY KEY autoincrement, name text, age integer, mobile text)";
database.execSQL(sql);
println("테이블 생성됨.");
} else {
println("먼저 데이터베이스를 오픈하세요.");
}
}
public void openDatabase(String databaseName) {
println("openDatabase() 호출됨.");
database = openOrCreateDatabase(databaseName, MODE_PRIVATE, null);
if (database != null) {
println("데이터베이스 오픈됨.");
}
}
public void println(String data) {
textView.append(data + "\n");
}
}
이미 있는 테이블 생성의 오류를 막으려면 [IF NOT EXISTS] 를 넣으면 된다.
SQLite: https://sqlitebrowser.org/{:target="_blank"}
헬퍼 클래스
배포 후 데이터베이스를 수정하기 위해 사용한다.
)
오픈 데이터베이스
public void openDatabase(String databaseName) {
println("openDatabase() 호출됨.");
DatabaseHelper helper = new DatabaseHelper(this, databaseName, null, 3);
database = helper.getWritableDatabase();
}
SQLite 오픈헬퍼
class DatabaseHelper extends SQLiteOpenHelper {
public DatabaseHelper(@Nullable Context context, @Nullable String name, @Nullable SQLiteDatabase.CursorFactory factory, int version) {
super(context, name, factory, version);
}
@Override
public void onCreate(SQLiteDatabase db) {
println("onCreate() 호출됨.");
String tableName = "customer";
String sql = "create table if not exists " + tableName + "(_id integer PRIMARY KEY autoincrement, name text, age integer, mobile text)";
db.execSQL(sql);
println("테이블 생성됨.");
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
println("onUpgrade 호출됨: " + oldVersion + ", " + newVersion);
if (newVersion > 1) {
String tableName = "customer";
db.execSQL("drop table if exists " + tableName);
println("테이블 삭제함.");
String sql = "create table if not exists " + tableName + "(_id integer PRIMARY KEY autoincrement, name text, age integer, mobile text)";
db.execSQL(sql);
println("테이블 새로 생성됨.");
}
}
}
인터넷 연결상태
NetworkStatus.java
public class NetworkStatus {
public static final int TYPE_WIFI = 1;
public static final int TYPE_MOBILE = 2;
public static final int TYPE_NOT_CONNECTED = 3;
public static int getConnectivityStatus(Context context) {
ConnectivityManager manager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo networkInfo = manager.getActiveNetworkInfo();
if (networkInfo != null) {
int type = networkInfo.getType();
if (type == ConnectivityManager.TYPE_WIFI) {
return TYPE_WIFI;
}
}
return TYPE_NOT_CONNECTED;
}
}
MainActivity.java 내 버튼
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
int status = NetworkStatus.getConnectivityStatus(getApplicationContext());
if (status == NetworkStatus.TYPE_MOBILE) {
textView.setText("모바일로 연결됨.");
} else if (status == NetworkStatus.TYPE_WIFI) {
textView.setText("무선랜으로 연결됨.");
} else {
textView.setText("연결안됨.");
}
}
});
정리
데이터베이스 생성
public class AppHelper {
private static final String TAG = "AppHelper";
private static SQLiteDatabase database;
private static String createTableOutlineSql = "create table if not exists outline" +
"(" +
" _id integer PRIMARY KEY autoincrement, " +
" id integer, " +
" title_eng text, " +
" dataValue text, " +
" user_rating float, " +
" audoence_rating float, " +
" reviwer_rating float, " +
" reservation_rate float, " +
" reservation_grade integer, " +
" grade integer, " +
" thumb text, " +
" image text" +
")";
public static void openDatabase(Context context, String databaseName) {
println("openDatabase 호출됨.");
try {
database = context.openOrCreateDatabase(databaseName, Context.MODE_PRIVATE, null);
if (database != null) {
println("데이터베이스 " + databaseName + " 오픈됨.");
}
} catch (Exception e) {
e.printStackTrace();
}
}
public static void createTable(String tableName) {
println("createTable 호출됨: " + tableName);
if (database != null) {
if (tableName.equals("outline")) {
database.execSQL(createTableOutlineSql);
println("outline 테이블 생성 요청됨.");
}
} else {
println("데이터베이스를 먼저 오픈하세요.");
}
}
public static void println(String data) {
Log.d(TAG, data);
}
}
MainActivity.java
AppHelper.openDatabase(getApplicationContext(), "movie");
AppHelper.createTable("outline");
Q. 가상에뮬레이터에서 LTE 연결방법은?
A.
7. 멀티미디어
카메라
Mainfest
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
build.gradle
targetSdkVersion 22
MainActivity.java
public class MainActivity extends AppCompatActivity {
ImageView imageView;
private File file;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
imageView = findViewById(R.id.imageView);
File sdcard = Environment.getExternalStorageDirectory();
file = new File(sdcard, "capture.jpg");
Button button = findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
capture();
}
});
}
private void capture() {
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
intent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(file));
startActivityForResult(intent, 101);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == 101 && resultCode == Activity.RESULT_OK) {
BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = 8;
Bitmap bitmap = BitmapFactory.decodeFile(file.getAbsolutePath(), options);
imageView.setImageBitmap(bitmap);
}
}
}
Q. context 란?
A.
Q. call back 메소드란?
A.
앱 내에 카메라 넣기
Mainfest
<uses-permission android:name="android.permission.CAMERA"/>
SurfaceView.java
public class CameraSurfaceView extends SurfaceView implements SurfaceHolder.Callback {
SurfaceHolder holder;
Camera camera = null;
public CameraSurfaceView(Context context) {
super(context);
init(context);
}
public CameraSurfaceView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
private void init(Context context) {
holder = getHolder();
holder.addCallback(this);
}
@Override
public void surfaceCreated(@NonNull SurfaceHolder holder) {
camera = Camera.open();
try {
camera.setPreviewDisplay(holder);
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void surfaceChanged(@NonNull SurfaceHolder holder, int format, int width, int height) {
camera.startPreview();
}
@Override
public void surfaceDestroyed(@NonNull SurfaceHolder holder) {
camera.stopPreview();
camera.release();
camera = null;
}
public boolean capture(Camera.PictureCallback callback) {
if (camera != null) {
camera.takePicture(null, null, callback);
return true;
} else {
return false;
}
}
}
MainActivity.java
public class MainActivity extends AppCompatActivity {
ImageView imageView;
CameraSurfaceView surfaceView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
imageView = findViewById(R.id.imageView);
surfaceView = findViewById(R.id.surfaceView);
Button button = findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
capture();
}
});
}
public void capture() {
surfaceView.capture(new Camera.PictureCallback() {
@Override
public void onPictureTaken(byte[] data, Camera camera) {
BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = 8;
Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length);
imageView.setImageBitmap(bitmap);
camera.startPreview();
}
});
}
}
음악&동영상
)
URL 에 지정해놓는다.
에뮬레이터로는 에러가 날 수 있습니다.
public class MainActivity extends AppCompatActivity {
public static String url = "http://sites.google.com/site/ubiaccessmobile/sample_audio.amr";
MediaPlayer player;
int position = 0;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button button_play = findViewById(R.id.play);
button_play.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
playAudio();
}
});
Button button_pause = findViewById(R.id.pause);
button_pause.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
pauseAudio();
}
});
Button button_replay = findViewById(R.id.replay);
button_replay.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
resumeAudio();
}
});
Button button_stop = findViewById(R.id.stop);
button_stop.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
stopAudio();
}
});
}
public void stopAudio() {
if (player != null && player.isPlaying()) {
player.stop();
Toast.makeText(this, "정지됨.", Toast.LENGTH_SHORT).show();
}
}
public void resumeAudio() {
if (player != null && !player.isPlaying()) {
player.seekTo(position);
player.start();
Toast.makeText(this, "재시작됨.", Toast.LENGTH_SHORT).show();
}
}
public void pauseAudio() {
if (player != null) {
position = player.getCurrentPosition();
player.pause();
Toast.makeText(this, "일시정지됨.", Toast.LENGTH_SHORT).show();
}
}
public void playAudio() {
closePlayer();
try {
player = new MediaPlayer();
player.setDataSource(url);
player.prepare();
player.start();
Toast.makeText(this, "재생 시작됨.", Toast.LENGTH_SHORT).show();
} catch (IOException e) {
e.printStackTrace();
}
}
public void closePlayer() {
if (player != null) {
player.release();
player = null;
}
}
}
동영상재생
샘플비디오: http://sites.google.com/site/ubiaccessmobile/sample_video.mp4{:target="_blank"}
public class MainActivity extends AppCompatActivity {
VideoView videoView;
public static String url = "http://sites.google.com/site/ubiaccessmobile/sample_video.mp4";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
videoView = findViewById(R.id.videoView);
MediaController controller = new MediaController(this);
videoView.setMediaController(controller);
videoView.setVideoURI(Uri.parse(url));
videoView.requestFocus();
videoView.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
@Override
public void onPrepared(MediaPlayer mp) {
Toast.makeText(MainActivity.this, "동영상 준비됨.", Toast.LENGTH_SHORT).show();
}
});
Button button = findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
videoView.seekTo(0);
videoView.start();
}
});
}
}
음성녹음
과정
- 미디어리코더 객체 생성
- 오디오 입력 및 출력형식 설정
- 오디오 인코더와 파일 지정
- 녹음 시작
- 매니페스트에 권한 설정
Manifest
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
MainActivity.java
public class MainActivity extends AppCompatActivity {
public static String url = "http://sites.google.com/site/ubiaccessmobile/sample_audio.amr";
MediaPlayer player;
MediaRecorder recorder;
int position = 0;
private String filename;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
File sdcard = Environment.getExternalStorageDirectory();
File file = new File(sdcard, "recorded.mp4");
filename = file.getAbsolutePath();
Log.d("MainActivity", "저장할 파일명: " +filename);
Button button_play = findViewById(R.id.play);
button_play.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
playAudio();
}
});
Button button_pause = findViewById(R.id.pause);
button_pause.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
pauseAudio();
}
});
Button button_replay = findViewById(R.id.replay);
button_replay.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
resumeAudio();
}
});
Button button_stop = findViewById(R.id.stop);
button_stop.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
stopAudio();
}
});
Button button_record = findViewById(R.id.record);
button_record.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
recordAudio();
}
});
Button button_stopRecord = findViewById(R.id.stopRecord);
button_stopRecord.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
stopRecording();
}
});
}
private void stopRecording() {
if (recorder != null) {
recorder.stop();
recorder.release();
recorder = null;
Toast.makeText(this, "녹음 중지됨.", Toast.LENGTH_SHORT).show();
}
}
public void recordAudio() {
recorder = new MediaRecorder();
recorder.setAudioSource(MediaRecorder.AudioSource.MIC);
recorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
recorder.setAudioEncoder(MediaRecorder.AudioEncoder.DEFAULT);
recorder.setOutputFile(filename);
try {
recorder.prepare();
} catch (IOException e) {
e.printStackTrace();
}
recorder.start();
Toast.makeText(this, "녹음 시작됨.", Toast.LENGTH_SHORT).show();
}
public void stopAudio() {
if (player != null && player.isPlaying()) {
player.stop();
Toast.makeText(this, "정지됨.", Toast.LENGTH_SHORT).show();
}
}
public void resumeAudio() {
if (player != null && !player.isPlaying()) {
player.seekTo(position);
player.start();
Toast.makeText(this, "재시작됨.", Toast.LENGTH_SHORT).show();
}
}
public void pauseAudio() {
if (player != null) {
position = player.getCurrentPosition();
player.pause();
Toast.makeText(this, "일시정지됨.", Toast.LENGTH_SHORT).show();
}
}
public void playAudio() {
closePlayer();
try {
player = new MediaPlayer();
player.setDataSource(filename);
player.prepare();
player.start();
Toast.makeText(this, "재생 시작됨.", Toast.LENGTH_SHORT).show();
} catch (IOException e) {
e.printStackTrace();
}
}
public void closePlayer() {
if (player != null) {
player.release();
player = null;
}
}
}
리사이클러뷰
- 좌우로 리스트뷰를 표현하고 싶을 때 사용
- 상하스크롤도 지원된다.
MainActivity.java
public class MainActivity extends AppCompatActivity {
RecyclerView recyclerView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
recyclerView = findViewById(R.id.recyclerView);
LinearLayoutManager layoutManager = new LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false);
recyclerView.setLayoutManager(layoutManager);
SingerAdapter adapter = new SingerAdapter(getApplicationContext());
adapter.addItem(new SingerItem("소녀시대", "010-1000-1000"));
adapter.addItem(new SingerItem("걸스데이", "010-1000-2000"));
adapter.addItem(new SingerItem("여자친구", "010-1000-3000"));
recyclerView.setAdapter(adapter);
}
}
SingerItem.java
public class SingerItem {
String name;
String mobile;
public SingerItem(String name, String mobile) {
this.name = name;
this.mobile = mobile;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getMobile() {
return mobile;
}
public void setMobile(String mobile) {
this.mobile = mobile;
}
}
SingerAdapter.java
public class SingerAdapter extends RecyclerView.Adapter<SingerAdapter.ViewHolder> {
Context context;
ArrayList<SingerItem> items = new ArrayList<>();
public SingerAdapter(Context context) {
this.context = context;
}
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View itemView = inflater.inflate(R.layout.singer_item, parent, false);
return new ViewHolder(itemView);
}
@Override
public void onBindViewHolder(@NonNull SingerAdapter.ViewHolder holder, int position) {
SingerItem item = items.get(position);
holder.setItem(item);
}
@Override
public int getItemCount() {
return items.size();
}
public void addItem(SingerItem item) {
items.add(item);
}
public void addItems(ArrayList<SingerItem> items) {
this.items = items;
}
public SingerItem getItem(int position) {
return items.get(position);
}
public class ViewHolder extends RecyclerView.ViewHolder {
TextView textView;
TextView textView2;
public ViewHolder(@NonNull View itemView) {
super(itemView);
textView = itemView.findViewById(R.id.textView);
textView2 = itemView.findViewById(R.id.textView2);
}
public void setItem(SingerItem item) {
textView.setText(item.getName());
textView2.setText(item.getMobile());
}
}
}
singer_item.xml
Q. 인터페이스의 static 선언?
A. 인터페이스는 객체를 생성할 수 없으므로 static 은 의미가 없다.
Q. 인터페이스의 public 메소드?
A. 인터페이스의 모든 메소드는 public abstract 이며 생략된다.
선택되는 리사이클러뷰
MainActivity.java
adapter.setOnItemClickListener(new SingerAdapter.OnItemClickListener() {
@Override
public void onItemClick(SingerAdapter.ViewHolder holder, View view, int position) {
SingerItem item = adapter.getItem(position);
Toast.makeText(MainActivity.this, "아이템 선택됨: " + item.getName(), Toast.LENGTH_SHORT).show();
}
});
SingerAdapter.java
public class SingerAdapter extends RecyclerView.Adapter<SingerAdapter.ViewHolder> {
Context context;
ArrayList<SingerItem> items = new ArrayList<>();
OnItemClickListener listener;
public interface OnItemClickListener {
void onItemClick(ViewHolder holder, View view, int position);
}
public SingerAdapter(Context context) {
this.context = context;
}
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View itemView = inflater.inflate(R.layout.singer_item, parent, false);
return new ViewHolder(itemView);
}
@Override
public void onBindViewHolder(@NonNull SingerAdapter.ViewHolder holder, int position) {
SingerItem item = items.get(position);
holder.setItem(item);
holder.setOnItemClickListener(listener);
}
@Override
public int getItemCount() {
return items.size();
}
public void addItem(SingerItem item) {
items.add(item);
}
public void addItems(ArrayList<SingerItem> items) {
this.items = items;
}
public SingerItem getItem(int position) {
return items.get(position);
}
public void setOnItemClickListener(OnItemClickListener listener) {
this.listener = listener;
}
static class ViewHolder extends RecyclerView.ViewHolder {
TextView textView;
TextView textView2;
OnItemClickListener listener;
public ViewHolder(@NonNull View itemView) {
super(itemView);
textView = itemView.findViewById(R.id.textView);
textView2 = itemView.findViewById(R.id.textView2);
itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
int position = getAdapterPosition();
if (listener != null) {
listener.onItemClick(ViewHolder.this, v, position);
}
}
});
}
public void setItem(SingerItem item) {
textView.setText(item.getName());
textView2.setText(item.getMobile());
}
public void setOnItemClickListener(OnItemClickListener listener) {
this.listener = listener;
}
}
}
포토뷰&유튜브
정리
Photo View
build.gradle(project)
allprojects {
repositories {
maven {url "https://jitpack.io"}
}
}
build.gradle(app)
implementation 'com.github.chrisbanes:PhotoView:2.1.3'
에러
Execution failed for task ':app:checkDebugDuplicateClasses'.
> A failure occurred while executing com.android.build.gradle.internal.tasks.CheckDuplicatesRunnable
* Try:
Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output. Run with --scan to get full insights.
와 같은 에러가 난다면 gradle.properties 에 다음과 같은 코드 추가
android.enableJetifier=true
MainActivity.java
PhotoView photoView = findViewById(R.id.photoView);
photoView.setImageResource(R.drawable.black_widow);
유튜브 연결
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String url = editText.getText().toString();
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
startActivity(intent);
}
});
8. 애니메이션
애니메이션
1. 스레드 애니메이션
public class MainActivity extends AppCompatActivity {
ArrayList<Drawable> imageList = new ArrayList<>();
ImageView imageView;
Handler handler = new Handler();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
imageView = findViewById(R.id.imageView);
Resources res = getResources();
imageList.add(res.getDrawable(R.drawable.ic_launcher_foreground));
imageList.add(res.getDrawable(R.drawable.ic_launcher_background));
imageList.add(res.getDrawable(R.mipmap.ic_launcher_round));
Button button = findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
AnimThread thread = new AnimThread();
thread.start();
}
});
}
class AnimThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
int index = i % 3;
Drawable drawable = imageList.get(index);
handler.post(new Runnable() {
@Override
public void run() {
imageView.setImageDrawable(drawable);
}
});
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
2. 트윈 애니메이션 (Tweened Animation)
- 뷰 애니메이션이라고도 하며, 보여줄 대상을 적절하게 연산한 후 그 결과를 연속적으로 디스플레이하는 방식임
- 애니메이션 대상과 변환방식을 지정하면 애니메이션 효과를 낼 수 있도록 만들어 줌
- 따라서 프레임 애니메이션처럼 변경하면서 보여줄 각각의 이미지를 추가할 필요없이 대상만 지정하면 시스템 내부적으로 적절하게 연산하는 과정을 거치게 됨
애니메이션을 위한 액션정보
- XML 리소스로 정의하거나 자바코드에서 직접 객체로 만듬
- 애니메이션을 위한 XML 파일은 [/res/anim] 폴더의 밑에 두어야 하며 확장자를 xml 로 함
- 리소스로 포함된 애니메이션 액션정의는 다른 리소스와 마찬가지로 빌드할 때 컴파일되어 설치파일에 포함됨
대상과 애니메이션 효과
MainActivity.java 버튼 클릭
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Animation scale = AnimationUtils.loadAnimation(getApplicationContext(), R.anim.scale);
v.startAnimation(scale);
}
});
scale.xml Anim 디렉토리 내부
<scale
android:pivotX="50%"
android:pivotY="50%"
android:fromXScale="1.0"
android:fromYScale="1.0"
android:toXScale="3.0"
android:toYScale="3.0"
android:duration="1500"/>
<scale
android:startOffset="1500"
android:pivotX="50%"
android:pivotY="50%"
android:fromXScale="1.0"
android:fromYScale="1.0"
android:toXScale="0.3"
android:toYScale="0.3"
android:duration="1500"/>
인터폴레이터
- accelerate_interpolator: 애니메이션 효과를 점점 빠르게
- decelerate-interpolator: 애니메이션 효과를 점점 느리게
- accelerate_decelerate_interpolator: 애니메이션 효과를 점점 빠르다가 느리게
- anticipate_interpolator: 시작위치에서 조금 뒤로 당겼다가 시작하도록
- overshoot_interpolator: 종료위치에서 조금 지나쳤다가 종료되도록
트윈 애니메이션이 스레드 애니메이션보다 성능면 등이 더 좋다.
페이지 슬라이딩
100%p
오른쪽 끝
0%p
왼쪽 끝
MainActivity.java
public class MainActivity extends AppCompatActivity {
LinearLayout page;
Animation translateLeft;
Animation translateRight;
Boolean isPageOpen = false;
private Button button;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
page = findViewById(R.id.page);
translateLeft = AnimationUtils.loadAnimation(this, R.anim.translate_left);
translateRight = AnimationUtils.loadAnimation(this, R.anim.translate_right);
SlidingAnimationListener listener = new SlidingAnimationListener();
translateLeft.setAnimationListener(listener);
translateRight.setAnimationListener(listener);
button = findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (isPageOpen) {
page.startAnimation(translateRight);
button.setText("열기");
} else {
button.setText("닫기");
page.setVisibility(View.VISIBLE);
page.startAnimation(translateLeft);
}
}
});
}
class SlidingAnimationListener implements Animation.AnimationListener {
@Override
public void onAnimationStart(Animation animation) {
}
@Override
public void onAnimationEnd(Animation animation) {
if (isPageOpen) {
page.setVisibility(View.INVISIBLE);
isPageOpen = false;
} else {
isPageOpen = true;
}
}
@Override
public void onAnimationRepeat(Animation animation) {
}
}
}
translate_left.xml
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate
android:fromXDelta="100%p"
android:toXDelta="0%p"
android:duration="1500"/>
</set>
translate_right.xml
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate
android:fromXDelta="0%p"
android:toXDelta="100%p"
android:duration="1500"/>
</set>
스플래시
layout 을 만들어 setContentView 해도 되고 테마로 설정해도 된다.
splash_base.xml: drawable 내부 새 xml
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<gradient
android:startColor="#FF3E50B4"
android:centerColor="#FF7288DB"
android:endColor="#FF7288DB"
android:angle="90"
android:centerY="0.5"/>
<corners android:radius="0dp"/>
</shape>
splash_background.xml: drawable 내부 새 xml
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/splash_base"/>
<item android:top="210dp"
android:drawable="@drawable/ic_launcher_foreground"
android:gravity="center">
</item>
</layer-list>
themes.xml: values 폴더 내부
<style name="SplashTheme" parent="Theme.AppCompat.NoActionBar">
<item name="android:windowBackground">@drawable/splash_background</item>
</style>
SplashActivity.java
public class SplashActivity extends AppCompatActivity {
Handler handler = new Handler();
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
handler.postDelayed(new Runnable() {
@Override
public void run() {
Intent intent = new Intent(getApplicationContext(), MainActivity.class);
startActivity(intent);
finish();
}
}, 1000);
}
}
Manifest
<activity android:name=".SplashActivity"
android:theme="@style/SplashTheme">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
초기 화면을 SplashActivity 로 변경 후 테마를 SplashTheme 으로 변경
정리
public class MainActivity extends AppCompatActivity {
private Animation translateUp;
private Animation translateDown;
LinearLayout menuContainer;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
translateUp = AnimationUtils.loadAnimation(getApplicationContext(), R.anim.translate_up);
translateDown = AnimationUtils.loadAnimation(getApplicationContext(), R.anim.translate_down);
translateUp.setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
}
@Override
public void onAnimationEnd(Animation animation) {
menuContainer.setVisibility(View.INVISIBLE);
}
@Override
public void onAnimationRepeat(Animation animation) {
}
});
menuContainer = findViewById(R.id.menuContainer);
Button button = findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (menuContainer.getVisibility() == View.VISIBLE) {
menuContainer.startAnimation(translateUp);
} else {
menuContainer.setVisibility(View.VISIBLE);
menuContainer.startAnimation(translateDown);
}
}
});
}
}
강사쌤은 isShown 이라는 불린 값을 정의해서 사용했지만 getVisibility 와 View.VISIBLE 함수를 사용하는 것이 더 적절한 것 같아 수정해서 작성했습니다.