(初心者向け)AndroidStudioを使ってギャラリーアプリを作る

### 環境

- Windows10 21H2
- Android Studio Chipmunk | 2021.2.1 Patch 1

### 使用するライブラリ
- [glide](https://github.com/bumptech/glide)　4.13.2

今回は Java を使用して簡易的なギャラリーアプリを作っていきます。
完成するとこのようになります。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/986357/3425fefd-03d4-c2aa-e9ec-cbf35c35e78f.png"
width="200px"
alt="完成画像"
 />




\*記事中でこの部分が間違っているやこの方法の方が良いという部分がありましたら教えていただけると幸いです。

それでは始めましょう！

## 下準備
まずは、マニフェストファイルに`<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
`を`application`タグの上に書き足します。
次にbuild.gradleにglideを追加します。

## 作成編

``` activity_main.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"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recycler_img"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>
```
画面構成はシンプルでRecyclerViewが一つあるだけです。

次にRecyclerViewのアイテムのレイアウトを作っていきましょう。
``` image_item.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <ImageView
        android:id="@+id/img_thumbnail"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:scaleType="centerCrop"
        app:layout_constraintDimensionRatio="1:1"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>
```
ConstraintLayoutを親に持っていることでアスペクト比を指定できるようになるので（もしかしたら他の物でもできるかも）ImageViewに`app:layout_constraintDimensionRatio="1:1"`を指定して正方形にしています。


次にRecyclerViewを使用するためにViewHolderとAdapterを作成します。今回はコードがそこまで長くならないのでViewHolderとAdapterを一つのファイルに作ってしまいます。

``` ImageAdapter.java
public class ImageAdapter extends RecyclerView.Adapter<ImageAdapter.ImageViewHolder> {
    private Context context;
    private List<String> pathList;

    public ImageAdapter(Context context, List<String> pathList) {
        this.context = context;
        this.pathList = pathList;
    }

    @NonNull
    @Override
    public ImageViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(context).inflate(R.layout.image_item, parent, false);
        return new ImageViewHolder(view);
    }

    @Override
    public void onBindViewHolder(@NonNull ImageViewHolder holder, int position) {
        String path = pathList.get(holder.getAdapterPosition());
        Glide.with(context).load(new File(path)).into(holder.img_thumbnail);
//        holder.img_thumbnail.setImageURI(Uri.parse(path));
    }

    @Override
    public int getItemCount() {
        return pathList.size();
    }

    class ImageViewHolder extends RecyclerView.ViewHolder {
        public ImageView img_thumbnail;
        public ImageViewHolder(@NonNull View itemView) {
            super(itemView);
            img_thumbnail = itemView.findViewById(R.id.img_thumbnail);
        }
    }
}
```
Adapterを簡単に説明すると、
onCreateViewHolder: ViewHolderを作成
onBindViewHolder:  リストのアイテムにデータを流しこむところ
getItemCount: リストのアイテム数を指定

　 

これでMainActivity.javaにコードを書いていく準備が終わりました。
それではMainActivity.javaにプログラムを書いていきましょう！

``` MainActivity.java
public class MainActivity extends AppCompatActivity {
    private RecyclerView recycler_img;
    private List<String> pathList;
    private ImageAdapter adapter;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        recycler_img = findViewById(R.id.recycler_img);
        recycler_img.setHasFixedSize(true);
        recycler_img.setLayoutManager(new GridLayoutManager(this, 3));

        if(ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
            // パーミッションがまだ許可されていなかった場合
            requestPermission.launch(Manifest.permission.READ_EXTERNAL_STORAGE);
        } else {
            // パーミッションが既に許可されていた場合
            appSetUP();
        }
    }

    @SuppressLint("Range")
    private void appSetUP() {
        // リストの初期化
        pathList = new ArrayList<>();

        // UIスレッドで処理すると画面が一瞬固まるので別スレッド
        ExecutorService service = Executors.newSingleThreadExecutor();
        service.execute(new Runnable() {
            @Override
            public void run() {
                // 画像の情報を取得してリストに追加する処理
                Cursor cursor = getApplicationContext().getContentResolver()
                        .query(
                                MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
                                null,
                                null,
                                null,
                                null
                        );

                while(cursor.moveToNext()) {
                    String path = cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media.DATA));
                    pathList.add(path);
                }
                // 画像の情報を取得してリストに追加する処理　ここまで

                // RecyclerViewのアダプターのインスタンスを作成
                adapter = new ImageAdapter(MainActivity.this, pathList);
                new Handler(Looper.getMainLooper()).post(new Runnable() {
                    @Override
                    public void run() {
                        // UIスレッド以外からUIを更新するとエラーが出るのでHandlerを使ってUIスレッドで処理する
                        recycler_img.setAdapter(adapter);
                    }
                });
            }
        });

    }

    // パーミッションをリクエストする処理
    // requestPermission.launch(Manifest.permission.READ_EXTERNAL_STORAGE); のようにしてリクエストする
    private final ActivityResultLauncher<String> requestPermission = registerForActivityResult(new ActivityResultContracts.RequestPermission(), result -> {
        if(result) {
            appSetUP();
        } else {
            Toast.makeText(this, "Read Storage permission is required", Toast.LENGTH_SHORT).show();
        }
    });
}
```

## おわりに
最後まで読んで下さりありがとうございました。
そしてお疲れ様です。
今回作ったものは画像一覧を表示させるだけのアプリですが、これをベースに自分で改造していくことも可能です。
自分で改造してうまくいくと自信が付くので時間がある人は是非試してみてください。