Material3なカルーセルを作る【Android Studio】【Java】

初心者です。  
AndroidStudioでMaterial3なカルーセルを作りたああああああああああああああい！！  
ということで作っていきます。  

# 完成した物  
![レコーディング-2023-12-21-223958.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3634181/f4aca775-79d7-55fa-6064-f27d5f9daf79.gif)
https://github.com/SousiOmine/Material3_Carousel_demo

# 参考にした資料  
https://www.youtube.com/watch?v=nV48OJQvvIc  
Atriiという方の解説動画です  
https://github.com/material-components/material-components-android/blob/master/docs/components/Carousel.md  
公式ドキュメントです  

# コピペで作っていく  

Material_Carouselという名前で新規プロジェクトを作成しました。  
試しにビルドしようとするとエラー発生！  
何もしていないのに壊れた！  
```error:エラーログ
An issue was found when checking AAR metadata:

  1.  Dependency 'androidx.activity:activity:1.8.0' requires libraries and applications that
      depend on it to compile against version 34 or later of the
      Android APIs.

      :app is currently compiled against android-33.

      Also, the maximum recommended compile SDK version for Android Gradle
      plugin 7.4.2 is 33.

      Recommended action: Update this project's version of the Android Gradle
      plugin to one that supports 34, then update this project to use
      compileSdkVerion of at least 34.

      Note that updating a library or application's compileSdkVersion (which
      allows newer APIs to be used) can be done separately from updating
      targetSdkVersion (which opts the app in to new runtime behavior) and
      minSdkVersion (which determines which devices the app can be installed
      on).
```
なんでテンプレのままなのに動かないんだよ〇〇〇！  
Gradleのバージョンを上げてcomplineSdkVersionを上げろと書いてありますがGradleは後回しにしても多分動くので  
build.gradle(app)を  
```gradle:build.gradle(app)
android {
    namespace 'com.example.material_carousel'
    //compileSdk 33
    compileSdk 34

    defaultConfig {
        applicationId "com.example.material_carousel"
        minSdk 24
        //targetSdk 33
        targetSdk 34
        versionCode 1
        versionName "1.0"

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }
```  
のように変えると正常に動作しました。  
カルーセルまだ全く触れてませんごめんね  
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3634181/b4f88d18-a71c-1bdc-748a-a8c9fcb891e3.png)  

Material3のライブラリを使えるようにgradleに追記します  
```gradle:build.gradle(app)
dependencies {
    implementation 'com.google.android.material:material:1.11.0'
}
```  
res/layoutにカルーセルに表示させる内容を定義する？xmlを書きます。というよりは公式ドキュメントからコピペします。  
私は画像と概要みたいなのを出したいのでテキストも出します  
画像にはとりあえres\drawableに適当な画像を置いて指定しておきました
```xml:carousel_layout.xml
<com.google.android.material.carousel.MaskableFrameLayout
    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:id="@+id/carousel_item_container"
    android:layout_width="150dp"
    android:layout_height="match_parent"
    android:layout_marginStart="4dp"
    android:layout_marginEnd="4dp"
    android:foreground="?attr/selectableItemBackground"
    app:shapeAppearance="?attr/shapeAppearanceCornerExtraLarge">
    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">
        <ImageView
            android:id="@+id/carousel_image_view"
            android:src="@drawable/karen"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:scaleType="centerCrop"/>
        <TextView
            android:id="@+id/carousel_text_view"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            android:textSize="32sp"
            android:textColor="@color/white"
            android:text="Karen"/>
    </androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.carousel.MaskableFrameLayout>
```  
activity_main.xmlにRecyclerViewを置きます。このときにlayoutManagerにmaterialライブラリのCarouselLayoutManagerを指定するらしい  
```xml:activity_main.xml
<androidx.recyclerview.widget.RecyclerView
        android:id="@+id/carousel_recycler_view"
        app:layoutManager="com.google.android.material.carousel.CarouselLayoutManager"
        tools:listitem="@layout/carousel_layout"
        android:layout_width="match_parent"
        android:layout_height="196dp"
        android:clipChildren="false"
        android:clipToPadding="false"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />
```  
ちなみにRecyclerViewから書き始めるとlistitemがないのでプレビューが出ず怖くなります  

これでactivity_main.xmlのデザインプレビューを見ると  
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3634181/8680f039-dad4-bc7a-43df-f2d254c59bab.png)
ぽい！ぽいぞ！  
ただ今はビルドして動かしてもなにも表示されません  
E/RecyclerView: No adapter attached; skipping layout とかぬかすので、adapterとやらを作ります  
参考にした資料の動画ではkotlinで書いててなにしてるのか全く分からなかったので、過去に自分で書いたJavaプログラムを見て思い出しながら書きました  

まずカルーセルに表示するデータを格納するクラスを作ります。画像と文字を格納できればいいです  
```java:CarouselModel.java
package com.example.material_carousel;

import android.graphics.Bitmap;

public class CarouselModel {
    public Bitmap image;
    public String text;

    public CarouselModel(Bitmap img, String txt) {
        this.image = img;
        this.text = txt;
    }
}
```  
次にViewHolderというわけわからんクラスも作ります。多分Adapterとカルーセルを結びつけるようなことをしています  
```java:CarouselHolder.java
package com.example.material_carousel;

import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;

import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;

public class CarouselHolder extends RecyclerView.ViewHolder{
    public ImageView imageView;
    public TextView textView;

    public CarouselHolder(@NonNull View itemView) {
        super(itemView);
        imageView = itemView.findViewById(R.id.carousel_image_view);
        textView = itemView.findViewById(R.id.carousel_text_view);
    }
}
```  
そしてAdapterを作ります。なんかいろいろするやつです  
```java:CarouselAdapter.java
package com.example.material_carousel;

import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Toast;

import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;

import java.util.ArrayList;
import java.util.List;

public class CarouselAdapter extends RecyclerView.Adapter<CarouselHolder> {
    ArrayList<CarouselModel> models;
    MainActivity main_activity;
    public CarouselAdapter(ArrayList<CarouselModel> list, MainActivity ma)
    {
        this.models = list;
        this.main_activity = ma;
    }

    @NonNull
    @Override
    public CarouselHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        //なにかよくわかっていない
        View inflate = LayoutInflater.from(parent.getContext()).inflate(R.layout.carousel_layout, parent, false);
        CarouselHolder carouselHolder = new CarouselHolder(inflate);
        return carouselHolder;
    }

    @Override
    public void onBindViewHolder(@NonNull CarouselHolder holder, int position) {
        //各項目に必要な要素をセットする
        holder.imageView.setImageBitmap(models.get(position).image);
        holder.textView.setText(models.get(position).text);
        holder.imageView.setOnClickListener(new View.OnClickListener() {
            public void onClick(View v) {
                tapFunc();
            }
        });
    }
    
    private void tapFunc()  //要素が押されたときに実行するやつをここに書く
    {
        Toast.makeText(main_activity, "九条カレン", Toast.LENGTH_SHORT).show();
    }

    @Override
    public int getItemCount() {
        return models.size();
    }
}
```  
で、最後にアダプターをコードで指定してやります  
今回はMainActivityにカルーセルを出す想定なのでMainActivityに書いていきます  
```java:MainActivity.java
package com.example.material_carousel;

import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.RecyclerView;

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Bundle;

import java.util.ArrayList;

public class MainActivity extends AppCompatActivity {
    ArrayList<CarouselModel> models;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        models = new ArrayList<>();
        Bitmap bmp = BitmapFactory.decodeResource(getResources(), R.drawable.karen);
        //使った画像がでかすぎて何もしないとエラーを吐くので小さくしてます
        bmp = Bitmap.createScaledBitmap(bmp, 500, 500, false);
        models.add(new CarouselModel(bmp, "Karen1"));
        models.add(new CarouselModel(bmp, "Karen2"));
        models.add(new CarouselModel(bmp, "Karen3"));
        models.add(new CarouselModel(bmp, "Karen4"));
        models.add(new CarouselModel(bmp, "Karen5"));

        RecyclerView recyclerView = findViewById(R.id.carousel_recycler_view);
        //アダプターを指定
        recyclerView.setAdapter(new CarouselAdapter(models, this));
    }
}
```  
ここまでいけばもう動くはず！  
これは一体、どうなっちゃうんだ～～！？！？  
![レコーディング-2023-12-21-222511.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3634181/c88b661a-a88b-6826-6d57-9b070383d29f.gif)
うごいたあああああああああああ！  
でもなんか...ダサくない？  

テーマを変えるのを忘れていました  
```xml:res/values/themes.xml
<style name="Theme.Material_Carousel" parent="Theme.Material3.Light">
以下略
```  
あとRecyclerViewにpadding(隙間)を設定しました  
![レコーディング-2023-12-21-223958.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3634181/f4aca775-79d7-55fa-6064-f27d5f9daf79.gif)
おおお！Material3になりました！~~マテリアル3だせえええええ～~~  
# いかがでしたか？  
ここまで作ったやつはGitHubに上げました  
https://github.com/SousiOmine/Material3_Carousel_demo
というわけでカレンちゃんを眺められる世界最高のアプリが完成したのでここで終わります  
誰かの参考になれば幸いです