사진은 사진의 데이터 베이스의 주소를 줌 , 사진의 URI(contents) 주소를 줌 , 실제 주소가 아니다 디비의 리소스 주소
Glide API 사용
인텐트 생성 setType 필수!
대행사 생성 (ActivityResultLauncher)
대행사에 런처 부탁~!
명시적인 Intent 생성없이 사진선택 화면이 BottomSheet 형식으로 보여짐
ActivityResultLauncher 가 다름 제네릭이 PickVisualMediaRequest로 온다
package com.bsj0420.ex69photopickbyintent;
import androidx.activity.result.ActivityResult;
import androidx.activity.result.ActivityResultCallback;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.PickVisualMediaRequest;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.appcompat.app.AppCompatActivity;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.provider.MediaStore;
import android.widget.ImageView;
import android.widget.TextView;
import com.bumptech.glide.Glide;
import java.net.URI;
public class MainActivity extends AppCompatActivity {
TextView tv_uri;
ImageView iv;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
tv_uri = findViewById(R.id.tv_uri);
iv = findViewById(R.id.iv);
findViewById(R.id.btn_pick).setOnClickListener(view -> clickPick());
findViewById(R.id.btn_content).setOnClickListener(view -> clickContent());
findViewById(R.id.btn_document).setOnClickListener(view -> clickDocument());
findViewById(R.id.btn_pickVisualMedia).setOnClickListener(view -> clickPickVisualMedia());
findViewById(R.id.btn_mediastore).setOnClickListener(view -> clickMediastore());
}
// 사진 선택 결과를 받아오는 계약을 체겨라는 개행사 객체 등록
ActivityResultLauncher<Intent> imgPickResultLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), new ActivityResultCallback<ActivityResult>() {
@Override
public void onActivityResult(ActivityResult result) {
if(result.getResultCode() != RESULT_CANCELED) {
//선택된 사진정보를 가진 택배기사 소환
Intent intent = result.getData();
//택배기사에게 사진정보 URI 데이터를 달라고 요첨
Uri uri = intent.getData();
tv_uri.setText(uri.toString());
//이미지 뷰에 셋팅
//이미지 로드 라이브러리 사용(Glide(범프테크 거), Picasso)
Glide.with(MainActivity.this).load(uri).into(iv);
}
}
});
private void clickPick() {
//ACTION_PICK : 갤러리 및 포토 앱에서만 선택, 미디어파일 폴더에서만 찾아옴
Intent intent = new Intent(Intent.ACTION_PICK).setType("image/*");
// setType(부를거/확장자) : 이거 안쓰면 에러 (* = 모든)
imgPickResultLauncher.launch(intent);
}
private void clickContent() {
//ACTION_GET_CONTENT : 모든 미디어 선택 앱에서 선택가능 (갤러리, 포토, 내문서 등)
Intent intent = new Intent(Intent.ACTION_GET_CONTENT).setType("image/*");
imgPickResultLauncher.launch(intent);
}
private void clickDocument() {
//ACTION_OPEN_DOCUMENT : 모든 문서 선택 앱에서 선택 - GET_CONTENT의 대체품
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT).setType("image/*");
imgPickResultLauncher.launch(intent);
}
private void clickPickVisualMedia() {
//명시적인 Intent 생성없이 사진선택 화면이 BottomSheet 형식으로 보여짐
// PickVisualMedia
pickMediaLauncher.launch(new PickVisualMediaRequest());
}
ActivityResultLauncher<PickVisualMediaRequest> pickMediaLauncher = registerForActivityResult(new ActivityResultContracts.PickVisualMedia(), new ActivityResultCallback<Uri>() {
@Override
public void onActivityResult(Uri result) { //곧바로 uri가 옴, 선택 안하면 null
tv_uri.setText(result.toString());
Glide.with(MainActivity.this).load(result).into(iv);
}
});
private void clickMediastore() {
//MediaStore 객체의 액션 ACTION_PICK --> PickVisualMedia와 같은 역할을 해줌
Intent intent = new Intent(MediaStore.ACTION_PICK_IMAGES); //셋타입 필요없음
imgPickResultLauncher.launch(intent);
}
}
https://github.com/Baseflow/PhotoView
사진 확대 API 추가
PhotoView는 ImageView를 상속받아 만듦
그냥 기본 view처럼 사용하면 됨
대행사 생성 : ActivityResultLauncher
-- result.getData() 한 애를
-- ClipData로 받아야함
💡 ClipData
- ClipData란 선택한걸 저장 복사한 조그마한 저장소
- ClipData은 사진만 선택하는 게 아니고 글자도 문서도 뭐든 올 수 있음
다중 선택한 사진을 보여주기 위해 uri 정보를 가진 arraylist와 이미지를 보여줄 layout을 만든 뒤 아답터에서 합쳐주고 리사이클러뷰나 뷰 페이저2로 보여줘야함
main 런처에서 리스트에 add 해주고 아답터한테 데이터 추가 됐다고 알려주기 : notifyDataSetChanged()
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="16dp"
tools:context=".MainActivity">
<Button
android:id="@+id/btn_openDcu"
android:text="action open document - multiple"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<Button
android:id="@+id/btn_PickMultip"
android:text="PickMultipleVisaul"
android:layout_below="@+id/btn_openDcu"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<Button
android:id="@+id/btn_mediaStore"
android:text="action MediaStorePiCK- multiple"
android:layout_below="@+id/btn_PickMultip"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<TextView
android:id="@+id/tv"
android:padding="16dp"
android:textColor="@color/black"
android:layout_centerHorizontal="true"
android:text="선택 개수"
android:layout_below="@+id/btn_mediaStore"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/pager"
android:layout_below="@+id/tv"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
<com.tbuonomo.viewpagerdotsindicator.WormDotsIndicator
android:id="@+id/dots_indcator"
android:clickable="true"
android:layout_centerHorizontal="true"
android:layout_alignParentBottom="true"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</RelativeLayout>
package com.bsj0420.ex70phtomultipickbyintent;
import androidx.activity.result.ActivityResult;
import androidx.activity.result.ActivityResultCallback;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.PickVisualMediaRequest;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.appcompat.app.AppCompatActivity;
import androidx.viewpager2.widget.ViewPager2;
import android.content.ClipData;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.provider.MediaStore;
import android.widget.TextView;
import com.tbuonomo.viewpagerdotsindicator.WormDotsIndicator;
import java.util.ArrayList;
import java.util.List;
public class MainActivity extends AppCompatActivity {
TextView tv;
//이미지 담을 리스트
ArrayList<Uri> uriArrayList = new ArrayList<>();
ImgAdapter adapter;
ViewPager2 pager;
WormDotsIndicator dotsIndicator;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
tv = findViewById(R.id.tv);
findViewById(R.id.btn_openDcu).setOnClickListener(v-> clickOpenDocu());
findViewById(R.id.btn_PickMultip).setOnClickListener(v-> clickPickMulti());
findViewById(R.id.btn_mediaStore).setOnClickListener(v-> clickMediaStore());
//아답터 연결
pager = findViewById(R.id.pager);
adapter = new ImgAdapter(this,uriArrayList);
pager.setAdapter(adapter);
//인디 케이터 dots_indcator
//dotsIndicator.attachTo(viewPager);
dotsIndicator = findViewById(R.id.dots_indcator);
dotsIndicator.attachTo(pager); // 연동 끝
}
//여러 사진 선택 결과를 돌려받는 계약 체결 대행사 객체 등록
ActivityResultLauncher<Intent> resultLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> {
if(result.getResultCode() != RESULT_CANCELED) { //취소하고 돌아오지 않을 때
//사진 URI들 가져온 택배기사 소환
Intent intent = result.getData();
ClipData clipData = intent.getClipData();
//ClipData : 선택한걸 저장 복사한 조그마한 저장소
//ClipData은 사진만 선택하는 게 아니고 글자도 문서도 뭐든 올 수 있음
int size = clipData.getItemCount(); //아이템 개수
// clipData.getItemAt(0).getUri()
// getItemAt 은 뭐든 올 수 있어서
//uri 정보를 가진 item arraylist만들고 아답터로 만든 뒤 리사이클러뷰나 뷰 페이저2로 보여준다
//!!이미지 셋!!
for (int i=0; i <size; i++){
uriArrayList.add( clipData.getItemAt(i).getUri() ); //
}
tv.setText("선택개수 : " + uriArrayList.size());
//아답터한테 알려주기 - 화면 갱신~!
adapter.notifyDataSetChanged();
//인디케이터
//원래는 탭으로 했는데 : dot indicator API 있음
}
});
private void clickOpenDocu() {
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT).setType("image/*").putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true);
resultLauncher.launch(intent);
}
private void clickPickMulti() {
muliplePickLauncher.launch(new PickVisualMediaRequest());
}
ActivityResultLauncher<PickVisualMediaRequest> muliplePickLauncher = registerForActivityResult(new ActivityResultContracts.PickMultipleVisualMedia(), new ActivityResultCallback<List<Uri>>() {
@Override
public void onActivityResult(List<Uri> result) { //리스트로 옴
for (Uri uri : result) {
uriArrayList.add(uri);
}
tv.setText(uriArrayList.size() + "");
adapter.notifyDataSetChanged();
}
});
private void clickMediaStore() {
//얘는 putExtra에 MediaStore.EXTRA_PICK_IMAGES_MAX 외대 개수 지정한다
Intent intent = new Intent(MediaStore.ACTION_PICK_IMAGES).putExtra(MediaStore.EXTRA_PICK_IMAGES_MAX, 10);
resultLauncher.launch(intent);
}
}
package com.bsj0420.ex70phtomultipickbyintent;
import android.content.Context;
import android.net.Uri;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import com.bumptech.glide.Glide;
import com.github.chrisbanes.photoview.PhotoView;
import java.util.ArrayList;
public class ImgAdapter extends RecyclerView.Adapter<ImgAdapter.VH> {
Context context;
ArrayList<Uri> items;
public ImgAdapter(Context context, ArrayList<Uri> items) {
this.context = context;
this.items = items;
}
@NonNull
@Override
public VH onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(context).inflate(R.layout.img_item,parent,false);
return new VH(view);
}
@Override
public void onBindViewHolder(@NonNull VH holder, int position) {
//holder.photoView.setImageURI(items.get(position));
Glide.with(context).load(items.get(position)).into(holder.photoView); //글라이더 쓰기
}
@Override
public int getItemCount() {
return items.size();
}
class VH extends RecyclerView.ViewHolder {
PhotoView photoView;
public VH(@NonNull View itemView) {
super(itemView);
photoView = itemView.findViewById(R.id.pv);
}
}
}
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.github.chrisbanes.photoview.PhotoView
android:id="@+id/pv"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</RelativeLayout>
https://github.com/tommybuonomo/dotsindicator
dotsIndicator.attachTo(viewPager2);
->결과 화면의 화질이 떨어짐(프리뷰 화면이라)
package com.bsj0420.ex71cameraapp;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.appcompat.app.AppCompatActivity;
import android.content.Intent;
import android.graphics.Bitmap;
import android.net.Uri;
import android.os.Bundle;
import android.provider.MediaStore;
import android.widget.ImageView;
import android.widget.Toast;
public class MainActivity extends AppCompatActivity {
ImageView iv;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
iv = findViewById(R.id.iv);
findViewById(R.id.camera_btn).setOnClickListener(v-> clickBtn());
}
private void clickBtn() {
//카메라 앱 실행
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
resultLauncher.launch(intent);
}
ActivityResultLauncher<Intent> resultLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> {
if(result.getResultCode() == RESULT_OK){
// 사진 촬영 파일의 Uri 얻어오기
Uri uri = result.getData().getData(); //앞 getData()는 Intent 뒤 getData()는 택배 박스
if(uri == null) Toast.makeText(this, "null", Toast.LENGTH_SHORT).show();
//요즘 핸드폰 프로그램으로 실행한 카메라앱에서 실행한 사진은 디바이스에 파일로 저장되지 않는다
//촬영한 사진의 프리뷰(썸네일/작은 이미지) 정보(URL)를 Bitmap 객체로 만들어서 Extra 데이터로 전달해줌
Intent intent = result.getData(); // 1. 인텐트 소환
Bundle bundle=intent.getExtras(); // 2. 인텐트 한테 번들 꺼내옴
Bitmap bm = (Bitmap)bundle.get("data"); // 3.비트맵으로 전환
iv.setImageBitmap(bm); //4.이미지 뷰에 적용
}
});
}
외부 저장소의 앱 전용 영역에 저장해 볼것임
이름이 동일하면 덮어쓰기가 되므로 이름의 겹치지 않도록하기
보통 : 오늘 날짜 - 시분초 이용
매니패스트에 등록
- 꼭 스타트 엔드로 나눠서 만들어야됨
- authorities : 유일키(보통 패키지명)
- name : 클래스이름
- exported : 절대 열어두면 안됨 false! (필수)
- grantUriPermissions : 임시퍼미션 해주겠다 (필수)
메타 데이터 만들기
이 프로 바이더가 공개 할 경로 지정
- name : 지정되어 있음 못 바꿈 android.support.FILE_PROVIDER_PATHS
- resource : 공개할 파일 경로 xml에 써두겠다
- Root element : paths 로 지정
res - xml - paths.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<!-- API 28버전 이하는 외부 저장소에 대한 '동적 퍼미션' 필요하다 -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="28"/>
<application
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.Ex72CameraApp_real"
tools:targetApi="31">
<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<!-- 파일 프로바이더 등록
authorities : 유일키(보통 패키지명)
name : 클래스이름
꼭 스타트 엔드로 나눠서 만들어야됨
exported : 절대 열어두면 안됨 false! (필수)
grantUriPermissions : 임시퍼미션 해주겠다 (필수)
-->
<provider
android:authorities="sam"
android:name="androidx.core.content.FileProvider"
android:exported="false"
android:grantUriPermissions="true">
<!-- 이 프로 바이더가 공개 할 경로 지정
name : 지정되어 있음 못 바꿈 android.support.FILE_PROVIDER_PATHS
resource : 공개할 파일 경로 어딘가에 써두겠다
-->
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/paths"/>
</provider>
</application>
</manifest>
uri 경로가 content로 시작되면 잘 한것임!!
package com.bsj0420.ex72cameraapp_real;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.content.FileProvider;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
import android.provider.MediaStore;
import android.widget.ImageView;
import android.widget.TextView;
import com.bumptech.glide.Glide;
import java.io.File;
import java.text.SimpleDateFormat;
import java.util.Date;
public class MainActivity extends AppCompatActivity {
ImageView iv;
TextView tv;
//촬영한 이미지가 저장될 파일의 Uri (콘텐츠 경로 - DB경로)
//1. 이미지 경로 참조 변수
Uri imgUri = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//개발자가 file로 저장되길 원한다면
//인텐트로 카메라 앱을 실행 할 때 추가 데이터를 설정해야함
iv = findViewById(R.id.iv);
tv = findViewById(R.id.tv);
findViewById(R.id.btn).setOnClickListener(view -> clickBtn());
}
private void clickBtn() {
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); //카메라 앱 실행 인텐트
//3. 개발자가 저장되길 원하는 위치에 파일 경로 URI를 만들어주는 기능(자체 제작) 호출
cerateImageUri();
//**중요** 촬영한 이미지를 file로 저장하도록 추가데이터로 [저장될 이미지의 Uri] 설정
//외부메모리 안에 공용폴더 중 픽쳐스 안에 파일 저장해보기
//2. 엑스트라에 주려면 imgUri이 필요해서 만들러 가자
if(imgUri != null) intent.putExtra(MediaStore.EXTRA_OUTPUT,imgUri); //imgUri가 Null이면 오류나서 if문 처리
resultLauncher.launch(intent);
}
//결과 받아오는 대행사
ActivityResultLauncher<Intent> resultLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> {
if(result.getResultCode() == RESULT_OK){
//카메라 앱이 촬영한 이미지를 EXTRA_OUTPUT 으로 지정한 imgUri에 저장했을 것임
Glide.with(this).load(imgUri).into(iv);
}
});
//3-1) 이미지의 경로 Uri를 생성사는 기능 메소드 설계
private void cerateImageUri() {
//저장될 파일의 경로 지정
//외부 저장소에 저장
//1. 외부 저장소의 앱 전용 영역
//2. 외부저장소의 미디어 영역
//1. 외부 저장소의 앱 전용 영역
File path = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES);
// getExternalStoragePublicDirectory은 이미지 폴더 같은 잘 알려진 곳
tv.setText(path.toString()); //=> 결과 :
//2) 파일명
//이름이 동일하면 덮어쓰기가 되므로 이름의 겹치지 않도록하기
//보통 : 오늘 날짜 - 시분초 이용
//String fileName = "Ex72_IMG_2023030713470.jpg";
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmms"); //오늘날짜 년월일 시분초
String fileName = "Ex72_"+ sdf.format(new Date()) + ".jpg";
//3) 경로 + 파일명.확장자
File file = new File(path + fileName); //실제 경로
tv.setText(file.toString());
//4) 카메라앱은 저장될 이미지의 실제 경로가 아니라 DB주소에 해당하는
// 콘텐츠 경로가 필요하다
//이 콘텐츠 경로를 Uri라고 부름
//실제 경로(File 클래스 객체)를 콘텐츠 경로(Uri 객체)로 변환
//누각 버전부터 다른앱에 DB 못 건드림
// Contents Provider (나의 DB정보를 다른 앱에게 주는 것) 사용해야됨!
// 다른 앱에게 파일의 접근을 허용하려면 Provider를 이용해야됨
// 그 중에서 파일에 대한 경로 제공 Provider은 이미 클래스로 설계되어 있다
// FileProvider
imgUri = FileProvider.getUriForFile(this,"sam",file);
//FileProvider.getUriForFile(contet, 제공하는자-암거나 써도 되나 디바이스 안에 유일해야함 보톤 패키지 이름씀 , file)
tv.setText(imgUri.toString());
}
}