국비교육(22-23)

102일차(1)/Android App(67) : 모바일 갤러리 기능 구현(1)

서리/Seori 2023. 3. 8. 01:12

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() 메소드 안에서 갤러리 목록을 요청하면서 아답타에게 모델이 연결되었다고 알리면 화면에 나타날 것이다.

 

(이어서 만들 예정)