① recylerview_item.xml 파일 만들기
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:background="@color/white"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="10dp">
<ImageView
android:id="@+id/iv_item_image"
android:layout_width="60dp"
android:layout_height="60dp"
android:scaleType="fitCenter"
android:src="@drawable/ic_image"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/tv_item_path"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:paddingHorizontal="5dp"
android:text="image path"
android:textColor="@color/black"
android:textSize="15sp"
app:layout_constraintBottom_toBottomOf="@+id/iv_item_image"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/iv_item_image"
app:layout_constraintTop_toTopOf="@+id/iv_item_image" />
</androidx.constraintlayout.widget.ConstraintLayout>
② RecyclerView를 적용할 xml 파일에 RecyclerView 태그 적용하기
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv_image_items"
android:layout_width="0dp"
android:layout_height="0dp"
android:orientation="vertical"
android:layout_marginVertical="10dp"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
app:layout_constraintBottom_toBottomOf="@+id/cardView"
app:layout_constraintEnd_toEndOf="@+id/cardView"
app:layout_constraintStart_toStartOf="@+id/cardView"
app:layout_constraintTop_toTopOf="@+id/cardView"
tools:listitem="@layout/rv_images" />
③ Data Model 생성하기
public class DataModel {
String imgUri;
String imagePath;
public String getImgUri() {
return imgUri;
}
public void setImgUri(String imgUri) {
this.imgUri = imgUri;
}
public String getImagePath() {
return imagePath;
}
public void setImagePath(String imagePath) {
this.imagePath = imagePath;
}
public DataModel(String imgUri, String imagePath) {
this.imgUri = imgUri;
this.imagePath = imagePath;
}
}
④ Adapter 클래스 생성하기
public class ImageRVAdapter extends RecyclerView.Adapter<ImageRVAdapter.ViewHolder> {
private ArrayList<DataModel> dataModels;
@NonNull
@Override
public ImageRVAdapter.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.rv_images, parent, false);
return new ViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull ImageRVAdapter.ViewHolder holder, int position) {
holder.onBind(dataModels.get(position));
}
public void setdataModels(ArrayList<DataModel> dataModels){
this.dataModels = dataModels;
notifyDataSetChanged();
}
@Override
public int getItemCount() {
if(dataModels == null) return 0;
return dataModels.size();
}
class ViewHolder extends RecyclerView.ViewHolder {
ImageView img;
TextView imgPath;
public ViewHolder(@NonNull View itemView) {
super(itemView);
img = (ImageView) itemView.findViewById(R.id.iv_item_image);
imgPath = (TextView) itemView.findViewById(R.id.tv_item_path);
}
void onBind(DataModel dataModel){
img.setImageURI(Uri.parse(dataModel.getImgUri()));
imgPath.setText(dataModel.getImagePath());
}
}
}
⑤ List와 RV Adapter 연결하기
public class MainActivity extends AppCompatActivity {
ActivityMainBinding binding;
private List<DataModel> dataModels = new ArrayList<>();
ImageRVAdapter adapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
RecyclerView recyclerView;
recyclerView = binding.rvImageItems;
adapter = new ImageRVAdapter();
recyclerView.setAdapter(adapter);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
adapter.setdataModels((ArrayList<DataModel>) dataModels);
...
⑥ 더미데이터 생성하기
DataModel dataModel = new DataModel(selectedImageURI, picturePath);
dataModels.add(dataModel);
adapter.setdataModels((ArrayList<DataModel>) dataModels);
① ImageRVAdapater에 아래의 내용을 추가한다.
public interface OnItemClickListener {
void onItemClick(View v, int pos);
}
private OnItemClickListener onItemClickListener = null;
public void setOnItemClickListener(OnItemClickListener listener) {
this.onItemClickListener = listener;
}
② ViewHolder의 생성자 안에 아래의 내용을 입력한다.
public ViewHolder(@NonNull View itemView) {
super(itemView);
img = (ImageView) itemView.findViewById(R.id.iv_item_image);
imgPath = (TextView) itemView.findViewById(R.id.tv_item_path);
deleteBtn = (ImageView) itemView.findViewById(R.id.iv_delete);
img.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
int position = getAdapterPosition();
if (position != RecyclerView.NO_POSITION) {
if (onItemClickListener != null) {
onItemClickListener.onItemClick(v, position);
}
}
}
});
...
deleteBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
int position = getAdapterPosition();
if (position != RecyclerView.NO_POSITION && onItemClickListener != null) {
dataModels.remove(position);
notifyItemRemoved(position);
notifyItemRangeChanged(position, dataModels.size());
}
}
});
...
③ RecyclerView를 적용할 Activity에서, setAdapter() 전에 아래의 내용을 입력한다.
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
RecyclerView recyclerView;
recyclerView = binding.rvImageItems;
adapter = new ImageRVAdapter();
adapter.setOnItemClickListener(new ImageRVAdapter.OnItemClickListener() {
@Override
public void onItemClick(View v, int pos) {
Intent intent = new Intent(MainActivity.this, DetailImageActivity.class);
intent.putExtra("imgUri", dataModels.get(pos).getImgUri());
startActivity(intent);
}
});
recyclerView.setAdapter(adapter);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
adapter.setDataModels((ArrayList<DataModel>) dataModels);
...
업로드 한 이미지가 있을 때에는 업로드 아이콘이 안 나타나다가, 이미지 목록이 비워지면 다시 나타나게 하는 방법에 대해 알아보자.
① Activity에서 Adapter로 UI 변경을 요청하기 위해 아래와 같은 메서드를 Activity에 정의한다.
public void updateImageViewVisibility() {
if (dataModels.size() == 0) {
binding.ivImageUpload.setVisibility(View.VISIBLE);
} else {
binding.ivImageUpload.setVisibility(View.INVISIBLE);
}
}
② Adapater 클래스의 삭제 버튼에 대한 클릭 이벤트 리스너의 아래의 내용을 추가한다.
eleteBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
...
((MainActivity) v.getContext()).updateImageViewVisibility();
갤러리 이미지에 접근하기 위해 필요한 권한을 요청하는 방법에 대해 알아보자.
① Manifest 파일에 아래의 내용을 추가한다.
<!-- 갤러리 권한 ~ver.10 -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<!-- 갤러리 권한 ver.11~ -->
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES"/>
② 권한 부여를 요청하고자하는 Activity 파일에 아래의 내용을 입력한다.
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
...
checkPermission();
}
...
private boolean checkPermission() {
String permission;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
permission = READ_MEDIA_IMAGES;
} else {
permission = READ_EXTERNAL_STORAGE;
}
int result = ContextCompat.checkSelfPermission(getApplicationContext(), permission);
if (result != PackageManager.PERMISSION_GRANTED) {
Log.e(TAG, "Permission for accessing media is not granted: " + result);
requestPermission();
} else {
Log.e(TAG, "Permission for accessing media is granted: " + result);
}
return result == PackageManager.PERMISSION_GRANTED;
}
private static final int PERMISSION_REQUEST_CODE = 200;
private void requestPermission() {
String permission;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
permission = READ_MEDIA_IMAGES;
} else {
permission = READ_EXTERNAL_STORAGE;
}
boolean shouldProvideRationale = shouldShowRequestPermissionRationale(permission);
Log.e(TAG, "requestPermission. Should provide rationale: " + shouldProvideRationale);
requestPermissions(new String[]{permission}, PERMISSION_REQUEST_CODE);
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == PERMISSION_REQUEST_CODE) {
// 사용자가 권한 요청에 대한 응답을 했을 때
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
// 권한이 허용된 경우
Log.e(TAG, "Permission granted");
} else if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_DENIED) {
// 권한이 거부된 경우
Log.e(TAG, "Permission denied");
Toast.makeText(this, "설정 또는 팝업에서 접근 권한을 허용해주세요.", Toast.LENGTH_SHORT).show();
}
}
}
}
앱 스키마란, 안드로이드 앱을 식별하는 데 사용되는 고유한 식별자를 의미한다. 앱 스키마는 App to App 및 Web to App 호출에 사용될 수 있다.
Manifest 파일에서 실행시키고자 하는 Activity 안에 아래의 내용을 입력한다.
<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:host="action" android:scheme="test-app" />
</intent-filter>
</activity>
이로써, 등록절차가 완료된다. Activity의 URI Scheme는 {shcheme}://{host}가 되므로, 여기서는 test-app://action
이 App Scheme이 된다.
App Scheme 등록이 완료되었다면, App을 호출하기 위한 별도의 App을 제작해야 한다. 새로운 프로젝트를 생성한 후 아래의 코드를 입력하자. 이는 버튼을 클릭했을 때, App Scheme에 대응하는 앱이 실행되는 상황을 가정한다.
btn.setOnClickListener {
String uriScheme = "test-app://action";
Intent intent = new Intent();
intent.setAction(Intent.ACTION_VIEW);
intent.setData(Uri.parse(uriScheme));
startActivity(intent);
}
이 때, 호출용 App과 App Scheme을 등록한 App 모두 디바이스에 설치되어 있어야 한다. 참고로, App Scheme을 등록한 이후에는, 오직 외부 호출을 통해서만 해당 앱을 실행할 수 있게 된다. 즉, 휴대폰에 설치는 되어있지만, 실물 App 형태로 존재하지는 않는 것이다.
① Build > Generate Signed Bundle / APK 버튼을 클릭한다.
② APK를 선택하고 Next 버튼을 클릭한다.
③ Key Store Path를 새로 등록하기 위해 Create new 버튼을 클릭한다.
④ Key store path 우측의 폴더 버튼을 클릭한다.
⑤ Key 파일의 경로와 이름을 지정한 뒤, OK 버튼을 클릭한다.
⑥ Password를 입력하고 Certificate 요소 중 적어도 하나의 정보를 입력한다.
⑦ 상황에 따라 Remember passwords에 체크 또는 체크 해제한 후, Next 버튼을 클릭한다.
⑧ release 선택 후 Create 버튼을 클릭한다.
⑨ 아래와 같이 완료 팝업이 뜨면, locate 버튼을 클릭한다.
⑩ 추출된 apk 파일을 확인할 수 있다.