102일차(1)/Android App(67) : 모바일 갤러리 기능 구현(1)
- String... 은 String 배열 [] 과 같다. 인자로 받는 패러미터의 개수를 동적으로 받을 수 있다는 뜻!
- 사진을 찍어서 업로드 버튼을 누르면 UploadTask를 통해서 전달한다.
- 안드로이드에서의 이 작업은 이런 폼을 원하는 위치로 제출(submit)하는 것과 같다.
- 위 form 요소를 java 코드로 구현한 것!
- PrintWriter 객체에 append해서 문자열을 전송한다.
- 이렇게 서버에 전송하면 서버는 이 약속된 키 값에 따라 동작하는 것이다.
- 개행기호, 구분선 등을 사용해 받는 정보를 이렇게 구성해볼 수 있다.
- 이 하나가 1바이트이다.(8개)
- 키 값으로 이런 데이터가 넘어왔다는 것을 서버에서 알게 된다.
- 안드로이드에서 보내면 서버에서 받아서 업로드해주는 것!
* Spring Boot의 업로드기능
- 업로드하면 이렇게 json 문자열이 응답된다.
- 성공/실패 여부를 응답하려면 이 onPostExecute 메소드 안에서 작업해주면 된다.
- 한번 업로드했으면 중복해서 동작하지 않도록 확인버튼을 띄워준다! (AlertDialog 객체)
- "업로드했습니다" 알림이 뜨도록 처리됨
- App을 실행하면 GalleryListActivity (목록) 가 활성화되고, 갤러리 목록을 출력한다.
- 사진 업로드하러 가기 버튼을 누르면 현재 MainActivity로 이동한다.
- 목록에서 특정 cell을 클릭하면 DetailActivity로 이동해서 원본 사진을 볼 수 있도록 한다.
- MainActivity 에서는 사진을 촬영하고 업로드하는 기능을 제공
- 업로드 후에는 다시 GalleryListAcitivity 로 이동하도록 한다.
- 이렇게 액티비티간 전환이 일어날 것이다.
- api 갤러리 리스트에 @GetMapping 으로 새로 만들어주기
- dto에 json 문자열이 응답
- { } 하나하나에는 정보가 들어가 있다!
- 이 하나하나의 정보를 안드로이드에서 추출해서 가져온다.
- mapper에서 전체목록을 리턴하는 SQL문을 작성할 것이다.
GalleryMapper
<select id="getListAll" resultType="galleryDto">
SELECT num, writer, caption, imagePath, regdate
FROM board_gallery
ORDER BY num DESC
</select>
GalleryDao
//모든 gallery List 가져오기
public List<GalleryDto> getListAll();
GalleryDaoImpl
@Override
public List<GalleryDto> getListAll() {
return session.selectList("gallery.getListAll");
}
- 추상메소드를 오버라이드해서 수정하기
GalleryController
package com.sy.boot07.gallery.controller;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.ModelAndView;
import com.sy.boot07.gallery.dao.GalleryDao;
import com.sy.boot07.gallery.dto.GalleryDto;
import com.sy.boot07.gallery.service.GalleryService;
@Controller
public class GalleryController {
@Autowired
private GalleryService service;
@Autowired
private GalleryDao dao;
//gallery list 페이지로 이동
@RequestMapping(value = "/gallery/list")
public String getList(HttpServletRequest request) {
//view 페이지에 사용될 데이터는 request 영역에 담는다.
service.getList(request);
return "gallery/list";
}
//gallery 사진 업로드 form 페이지로 이동
@RequestMapping(value = "/gallery/uploadform")
public String uploadForm() {
return "gallery/uploadform";
}
//gallery 사진 업로드 & DB 저장
@RequestMapping(value = "/gallery/upload")
public String upload(GalleryDto dto, HttpServletRequest request) {
//form 에서 dto 로 데이터 받아옴
//dto : caption, MultipartFile image 를 가지고 있다.
//request : imagePath 만드는데 사용, session 영역의 id 가져오는데 사용
service.saveImage(dto, request);
return "gallery/upload";
}
//gallery 사진 업로드 form - ajax form
@RequestMapping(value = "/gallery/ajax_form")
public String ajaxForm() {
return "gallery/ajax_form";
}
//gallery 사진 업로드 - ajax
//json 으로 return 할 것
@RequestMapping(value = "/gallery/ajax_upload")
@ResponseBody
public Map<String, Object> ajaxUpload(GalleryDto dto, HttpServletRequest request){
//form 에서 dto 로 데이터 받아옴
//dto : MultipartFile image 를 가지고 있다.
//request : imagePath 만드는데 사용, session 영역의 id 가져오는데 사용
//return : { "imagePath" : "/upload/123456img_name.png" } 형식의 JSON 응답
return service.uploadAjaxImage(dto, request);
}
//imagePath 구성 X -> dto 로 imagePath 를 받아서 DB 에 저장하기
@RequestMapping("/gallery/insert")
public String insert(GalleryDto dto, HttpServletRequest request) {
//dto : caption, imagePath 가지고 있다.
//request : dto 에 writer(id) 추가
service.insert(dto, request);
return "gallery/upload";
}
//gallery 게시글의 num 이 parameter get 방식으로 넘어온다.
//detail 페이지
@RequestMapping(value = "/gallery/detail", method = RequestMethod.GET)
public ModelAndView detail(ModelAndView mView, int num) {
//갤러리 detail 페이지에 필요한 data를 num 으로 가져와, ModelAndView 에 저장
service.getDetail(mView, num);
mView.setViewName("gallery/detail");
return mView;
}
//안드로이드 앱에서 사진을 업로드하는 요청을 처리하는 메소드
@PostMapping("/api/gallery/insert")
@ResponseBody
public Map<String, Object> apiInsert(GalleryDto dto, HttpServletRequest request){
/*
GalleryDto에 담긴 내용을 활용해서 이미지를 파일시스템에 저장하고 이미지 정보를 DB에도 저장한다.
*/
service.saveImage(dto, request);
Map<String, Object> map=new HashMap<>();
map.put("isSuccess", "true");
return map;
}
@GetMapping("/api/gallery/list")
@ResponseBody
public List<GalleryDto> apiList(){
return dao.getListAll();
}
}
- @Autowired Dao 추가해주고,
- getListAll() 메소드를 사용해서 ResponseBody로 읽어온다.
- 주소창에 경로를 직접 입력해보면 이렇게 갤러리가 JSON 문자열이 되어 출력되는 것을 확인할 수 있다.
- 새 액티비티 GalleryListActivity 생성
activity_gallery_list.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".GalleryListActivity"
android:orientation="vertical">
<ListView
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:id="@+id/listView"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<Button
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="사진찍기"
android:id="@+id/takePicBtn"/>
<Button
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="새로고침"
android:id="@+id/refreshBtn"/>
</LinearLayout>
</LinearLayout>
- 이러한 구조로 이미지 하나하나를 이 리스트뷰에 출력할 것이다!
- 저 하나의 cell을 직접 만들어야 한다.
- 저 리스트뷰에는 4가지 정보가 출력되어야 한다.
1)캡션 2)쟉성자 3)작성일 4)이미지 데이터
- 이런 방식으로... 셀의 레이아웃 설정이 필요
- res 폴더에서 우클릭-New Resource FIle
- 리니어 레이아웃으로 listview_cell.xml 생성 (xml파일의 이름은 대문자로 쓰면 안된다)
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="200dp">
<ImageView
android:layout_width="180dp"
android:layout_height="match_parent"
android:layout_margin="10dp"
android:id="@+id/imageView"/>
<LinearLayout
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="match_parent"
android:layout_margin="10dp"
android:orientation="vertical"
android:gravity="bottom">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="20sp"
android:text="작성자:admin"
android:id="@+id/writer"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="20sp"
android:text="어쩌구 저쩌구..."
android:id="@+id/caption"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="20sp"
android:text="2023.03.07 13:00"
android:id="@+id/regdate"/>
</LinearLayout>
</LinearLayout>
- 이 xml 파일 안에서 하나의 셀을 만들어주면 된다!
- Design 탭에서 디자인을 확인해보며 수정하기~!
- 아래 자식요소로 수직 배치하는 리니어레이아웃을 추가하고, TextView를 배치해준다.
- 글자크기는 sp 단위, 컨텐츠의 크기는 dp 단위이다.
- 대략 이런 형태로 만들어보았다.
- 안드로이드에 GalleryDto 생성
GalleryDto
package com.example.step25imagecapture;
public class GalleryDto {
private int num;
private String writer;
private String caption;
private String imagePath;
private String regdate;
public GalleryDto(){}
public GalleryDto(int num, String writer, String caption, String imagePath, String regdate) {
this.num = num;
this.writer = writer;
this.caption = caption;
this.imagePath = imagePath;
this.regdate = regdate;
}
public int getNum() {
return num;
}
public void setNum(int num) {
this.num = num;
}
public String getWriter() {
return writer;
}
public void setWriter(String writer) {
this.writer = writer;
}
public String getCaption() {
return caption;
}
public void setCaption(String caption) {
this.caption = caption;
}
public String getImagePath() {
return imagePath;
}
public void setImagePath(String imagePath) {
this.imagePath = imagePath;
}
public String getRegdate() {
return regdate;
}
public void setRegdate(String regdate) {
this.regdate = regdate;
}
}
- 리스트뷰에 연결할 아답타 만들기
GalleryAdapter
package com.example.step25imagecapture;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.TextView;
import com.bumptech.glide.Glide;
import java.util.List;
public class GalleryAdapter extends BaseAdapter {
private LayoutInflater inflater;
private List<GalleryDto> list;
private int layoutRes;
private Context context;
//생성자
public GalleryAdapter(Context context, int layoutRes, List<GalleryDto> list){
this.layoutRes=layoutRes;
this.list=list;
this.context=context;
//레이아웃 전개자 객체의 참조값을 얻어내서 필드에 저장
inflater=LayoutInflater.from(context);
}
//전체 아이템의 개수 리턴
@Override
public int getCount() {
return list.size();
}
//position에 해당하는 아이템 리턴
@Override
public Object getItem(int position) {
return list.get(position);
}
//position에 해당하는 아이템의 아이디(PK)
@Override
public long getItemId(int position) {
return list.get(position).getNum();
}
//position에 해당하는 View 를 리턴
@Override
public View getView(int position, View convertView, ViewGroup parent) {
if(convertView == null){
convertView=inflater.inflate(layoutRes,parent,false);
}
//position에 해당하는 GalleryDto 를 얻어내서
GalleryDto dto=list.get(position);
ImageView imageView=convertView.findViewById(R.id.imageView);
TextView textWriter=convertView.findViewById(R.id.writer);
TextView textCaption=convertView.findViewById(R.id.caption);
TextView textRegdate=convertView.findViewById(R.id.regdate);
textWriter.setText(dto.getWriter());
textCaption.setText(dto.getCaption());
textRegdate.setText(dto.getRegdate());
//Glide를 활용해서 imageView에 이미지 출력하기
Glide.with(context)
.load(dto.getImagePath())
.centerCrop()
.placeholder(R.drawable.ic_launcher_background)
.into(imageView);
return convertView;
}
}
- 추상클래스 BaseAdapter를 상속하고 Alt+Enter로 추상메소드(4개) 오버라이드!
- listview_cell.xml 을 전개해서 View로 바꾸기 위해 필요한 것이 이 레이아웃 전개자(layout inflater) 객체이다
- xml 파일을 해석해서 화면(View)에 나타내준다.
1) getCount()
- 연결하는 모델의 사이즈를 리턴
- 생성자로 들어온 list 값이 GalleryAdapter의 필드에 들어간다.
- getCount에서는 이 필드의 사이즈 값을 리턴한다.
2) getItem()
- 인덱스에 해당하는 아이템 리턴
3) getItemId()
- GalleryDto 의 번호를 리턴(num이 primary key이기 때문에)
- 리턴된 galleryDto의 번호을 찾아온다.
4) getView()
- 인덱스 값으로 GalleryDto를 얻어내서,
convertView에서 찾아온 imageView, textView로 아이디를 얻어오기(참조값)
- 각각의 TextView에 textWriter, dto 를 사용해서 출력해주면 된다!
- 그런데 이미지는 어떻게 얻어와야 할까?
- 원격지 서버의 이미지뷰에 경로를 전달한다고 되는 것이 아니다!
→ 경로를 사용해서 이미지파일을 다운받은 다응메, setImageBitMap() 으로 넣어주어야 한다.
- 추가로 한번 다운받은 것은 캐시 처리해서 두번세번 다운받지 않도록 해줄 예정!
- 이 복잡한 과정을 알아서 대신해주는 라이브러리가 있다!
- Glide Android 구글 검색
* 깃허브 링크 : https://github.com/bumptech/glide
- gradle, maven에서의 사용방식
- 이것만 모듈의 gradle 설정파일에 넣어주면 사용 가능하다.
dependencies {
implementation 'com.github.bumptech.glide:glide:4.15.0'
annotationProcessor 'com.github.bumptech.glide:compiler:4.15.0'
}
- dependencies 에 glide 를 추가해주었다. + sync now
- 그러면 이제 클래스 안에서 Glide 객체를 사용할 수 있다.
Glide.with()
- 어떤 것과 함께 사용할 것인지를 정하는 메소드
- 만약 액티비티와 사용한다고 하면, 액티비티가 비활성화되었을 경우에도 glide만 동작하지 않도록 with를 사용해서 무엇과 함께 동작할 것인지 넣어준다.
- 참조값으로는 이 context가 전달된다. 액티비티의 참조값!
- 액티비티는 context타입이기도 하므로 받을 수 있다.
- 이 Glide.with() 까지를 RequestManager 타입으로 사용할 수 있다.
- 문자열을 받아서 RequestBuilder 타입을 반환해주는 load 메소드 사용
- 사용하기 편리하게 만들어져 있다. 여러번 작성할 필요 없이 뒤에 점만 찍어서 새로운 옵션이나 기능들을 추가할 수 있다.
- imageView에서 저장한 사진들을 관리해준다.(?)
- 메소드 마지막에는 convertView를 리턴해주면 된다.
GalleryListActivity
package com.example.step25imagecapture;
import android.os.Bundle;
import android.view.View;
import androidx.appcompat.app.AppCompatActivity;
import com.example.step25imagecapture.databinding.ActivityGalleryListBinding;
import java.util.ArrayList;
import java.util.List;
public class GalleryListActivity extends AppCompatActivity implements View.OnClickListener{
ActivityGalleryListBinding binding;
//서버에서 받아온 갤러리 목록을 저장할 객체
List<GalleryDto> list=new ArrayList<>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//바인딩 객체의 참조값을 필드에 저장
binding=ActivityGalleryListBinding.inflate(getLayoutInflater());
//바인딩 객체를 이용해서 화면 구성
setContentView(binding.getRoot());
//listView에 연결할 아답타 객체 생성
GalleryAdapter adapter=new GalleryAdapter(this, R.layout.listview_cell, list);
//ListView에 아답타 연결하기
binding.listView.setAdapter(adapter);
//버튼에 리스너 등록하기
binding.takePicBtn.setOnClickListener(this);
binding.refreshBtn.setOnClickListener(this);
}
@Override
protected void onStart() {
super.onStart();
//원격지 서버로부터 갤러리 목록을 받아오는 요청을 한다.
}
//버튼을 눌렀을때 호출되는 메소드
@Override
public void onClick(View v) {
switch (v.getId()){
case R.id.takePicBtn:
break;
case R.id.refreshBtn:
break;
}
}
}
- 뷰바인딩으로 GalleryList를 넣어주기
- Adapter에 액티비티, layout Id와 서버에서 받아온 list를 담아주었다.
- Activity에 onClickListener 구현
- 각각의 버튼에 리스너를 연결해주고,
- onStart() 메소드 안에서 갤러리 목록을 요청하면서 아답타에게 모델이 연결되었다고 알리면 화면에 나타날 것이다.
(이어서 만들 예정)
'국비교육(22-23)' 카테고리의 다른 글
104일차(1)/Android App(69) : 모바일 갤러리 기능 구현(3) (0) | 2023.03.09 |
---|---|
103일차(1)/Android App(68) : 모바일 갤러리 기능 구현(2) (0) | 2023.03.09 |
101일차(1)/Android App(66) : 카메라 앱으로 사진 촬영, 저장(3) / 서버 전송 (0) | 2023.03.07 |
99일차(2)/Android App(65) : 카메라 앱으로 사진 촬영, 저장(2) (0) | 2023.03.04 |
99일차(1)/Android App(64) : 카메라 앱으로 사진 촬영, 저장 (0) | 2023.03.03 |