【Android】ViewPagerでスライドショーを実現する
- 使用するクラス
- 表示したいレイアウトにViewPagerを持たせる
- 「1ページ分」を表す各フラグメントを作る
- Adapterクラスを作る
- 画面に表示させる
- 完成
- 実験~コンパクトにしてみた~
- 追記・修正
使用するクラス
MainActivity | スライドショーを表示するアクティビティ |
---|---|
SlideFragment1 | スライド1ページ目を表すフラグメント |
SlideFragment2 | スライド2ページ目を表すフラグメント |
SlideFragment3 | スライド3ページ目を表すフラグメント |
SlidePagerAdapter | ViewPager(後述)のアダプターにセットするAdapterクラス |
それぞれのActivity、Fragmentに対応するレイアウトも使用します(∩´∀`)∩
表示したいレイアウトにViewPagerを持たせる
ViewPagerは、スライドショーを実装するために便利なViewです(/・ω・)/
デフォルトでページを切り替えるための画面スライドアニメーションが表示されるため、独自のアニメーションを作成する必要はありません。
では、MainActivityにViewPagerを持たせます。
<?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" android:orientation="vertical" tools:context=".MainActivity"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" android:layout_marginTop="10dp" android:layout_marginBottom="10dp" android:text="スライドショー" android:textSize="30dp" /> <androidx.viewpager.widget.ViewPager android:id="@+id/vpSlide" android:layout_width="match_parent" android:layout_height="match_parent" android:background="#ffb6c1" android:layout_marginBottom="10dp"/> </LinearLayout>
「1ページ分」を表す各フラグメントを作る
次は、先ほどのViewPagerエリアに表示される、各ページを表すフラグメントを1ページずつ作っていきます。
ViewPagerは、フラグメントをスライドアニメーションに合わせて次々に生成してエリアに表示させるため、
それぞれのページに対応したフラグメントが必要、ということですね(^_^)
例えば3ページある場合、3つのフラグメントを作ることになります。
(ただし、条件によってはそうしなくてもよい場合もあります。これは後述しますね!)
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> <ImageView android:layout_width="match_parent" android:layout_height="match_parent" android:src="@drawable/img_slide_1"/> </LinearLayout>
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> <ImageView android:layout_width="match_parent" android:layout_height="match_parent" android:src="@drawable/img_slide_2"/> </LinearLayout>
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> <ImageView android:layout_width="match_parent" android:layout_height="match_parent" android:src="@drawable/img_slide_3"/> </LinearLayout>
今回はシンプルに画像を表示するだけのレイアウトにしました('ω')
では、フラグメントクラスを作っていきます。
もちろん個別にファイルを作ってもいい(それが一般的?)と思うのですが、
なんかごちゃごちゃして嫌だったので1ファイルにまとめてみました(;'∀')
class SlideFragment1: Fragment() { override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? = inflater.inflate(R.layout.fragment_slide_1, container, false) } class SlideFragment2: Fragment() { override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? = inflater.inflate(R.layout.fragment_slide_2, container, false) } class SlideFragment3: Fragment() { override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? = inflater.inflate(R.layout.fragment_slide_3, container, false) }
各フラグメントクラスでは、レイアウトをView化してそのまま返していますね(∩´∀`)∩
Adapterクラスを作る
さて、これまでの作業で
「表示するエリアの確保(ViewPager)」
と
「表示するページの作成(Fragment)」
ができたわけですが、
当然これだけでは表示されません(;'∀')
ListViewやRecylerViewではAdapterを使ってViewとデータの橋渡しを行いますよね(/・ω・)/
それと同じように、ViewPagerでもAdapterを使用するわけです!
今回はFragmentStateAdapterを継承して独自のAdapterクラスを作成していきましょう。
FragmentStateAdapterは
フラグメントを使用して各ページを管理するPagerAdapterを継承しており、
フラグメントの状態の保存と復元も処理できるアダプターです(^_^)
class SlidePagerAdapter(fm: FragmentManager, private val list: List<Fragment>) : FragmentStatePagerAdapter(fm, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) { override fun getItem(position: Int): Fragment { return list[position] } override fun getCount(): Int { return list.size } }
<ポイントその1>
親クラス(FragmentStateAdapter)のコンストラクタに渡す値
①自身のクラスのコンストラクタに渡されたFragmentManager
②フラグメントのライフサイクルの挙動を表す定数
以前は①だけのコンストラクタを使用することもできていたようですが、
現在は非推奨となっており、②は必須となります!
②の定数は2種類あるようです。
BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT | 現在のフラグメントのみがLifecycle.State.RESUMED状態になり、 他のすべてのフラグメントは、Lifecycle.State.STARTEDで制限されます。 |
---|---|
BEHAVIOR_SET_USER_VISIBLE_HINT | すべてのフラグメントはLifecycle.State.RESUMED状態になり、現在のフラグメントが変更されたときFragment.setUserVisibleHint(boolean)へのコールバックがあります。非推奨 |
ただし、BEHAVIOR_SET_USER_VISIBLE_HINTは現在非推奨になっているため、実質指定できるのはBEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENTのみです。
<ポイントその2>
2つのオーバーライドメソッド
getItem(position: Int) | 表示するページとなるFragmentを返します。 |
---|---|
getCount() | リストのサイズを返します。 |
画面に表示させる
最後にやることは3つです。
「ページを表すフラグメントのリストを生成」
「独自アダプターにデータを渡してオブジェクト生成」
「ViewPagerにアダプターをセット」
では、コードを見てみましょう
class MainActivity : AppCompatActivity() { private lateinit var _list: List<Fragment> private lateinit var _adapter: SlidePagerAdapter override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) //ページを表すフラグメントのリストを生成 _list = mutableListOf( SlideFragment1(), SlideFragment2(), SlideFragment3() ) //独自アダプターにデータを渡してオブジェクト生成 _adapter = SlidePagerAdapter(supportFragmentManager, _list) //ViewPagerにアダプターをセット vpSlide.adapter = _adapter } }
これで完成です!!
では、画面を見てみましょう!
完成
見栄えの関係でピンク色のエリアは元に戻しています(∩´∀`)∩
実験~コンパクトにしてみた~
今回のように各ページの変化が「1つの画像リソースだけ」のような場合は、
もう少しコンパクトになる気がします。
どういうことかというと、
「リソースだけ動的に変えながら1つのフラグメントクラスから複数インスタンス生成したらいんでね?」
ってことです('ω')‼
ってことで
ちょっくら実験してみました。
いじるのはフラグメントレイアウトと、MainActivity.kt、SlideFragments.ktです。
まずはレイアウトから。
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> <ImageView android:id="@+id/ivSlide" android:layout_width="match_parent" android:layout_height="match_parent"/> </LinearLayout>
レイアウトをこれ1つにして、
「fragment_slide_2.xml」と「fragment_slide_3.xml」は削除しました('ω')ノ
次に、フラグメントクラス。
class SlideFragment : Fragment() { override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? { val imgResource = arguments?.getInt("imgResource") val view = inflater.inflate(R.layout.fragment_slide, container, false) val ivSlide: ImageView = view.findViewById(R.id.ivSlide) try { ivSlide.setImageResource(imgResource!!) } catch (e: Exception) { when(e){ is Resources.NotFoundException, is NullPointerException ->{ e.printStackTrace() ivSlide.setImageResource(R.drawable.img_error) } else ->{ throw e } } } return view } }
レイアウト同様、フラグメントクラスもこれ一つにして、これ以外は削除しました。
コンストラクタでリソースIDを受け取り、
ImageViewの画像リソースだけを動的に変更して返すコードにしました。
<ーー修正ーー>
AndroidDeveloperによるとFragmentのコンストラクタは空でないとNGとのことです(;_;)…
代わりに、Argumentから値を受け取るコードに修正しました(;'∀')
<-ー---->
受け取ったリソースIDはIntなので、変な値が渡された時の対処としてエラー用の画像を用意。これを表示するようにしています。
最後にMainActivity。
class MainActivity : AppCompatActivity() { private lateinit var _list: List<Fragment> private lateinit var _adapter: SlidePagerAdapter override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) _list = listOf( createNewSlideFragment(R.drawable.img_slide_1), createNewSlideFragment(R.drawable.img_slide_2), createNewSlideFragment(R.id.ivSlide) ) _adapter = SlidePagerAdapter(supportFragmentManager, _list) vpSlide.adapter = _adapter } private fun createNewSlideFragment(imgResource: Int): SlideFragment { val fragment = SlideFragment() val args = Bundle() args.putInt("imgResource", imgResource) fragment.arguments = args return fragment } }
リソースをArgumentsにセットしたフラグメントものを返すcreateNewSlideFragment()メソッドを作成しました('ω')ノ
これによって生成されたフラグメントを、リストに格納しています(∩´∀`)∩
また実験として、3つ目のインスタンスのコンストラクタ引数にはエラーになるような値を渡しています。
どうなるでしょうか?
コンパクトになりましたね(∩´∀`)∩
全てのパターンに当てはまるわけではないですが、
大まかなレイアウトが同じなら、ある程度応用できそうです(/・ω・)/
表示するページ数があらかじめ決まっている場合は
個別にレイアウトと対応クラスを作ってもいいと思いますが、
特に動的にページ数を変えたいなんて時には、
こちらの方法を使うとよいかもしれません(^_-)-☆
追記・修正
2020/2/14
ー 実験~コンパクトにしてみた~ にて、Fragmentのコンストラクタに値を渡していたため修正。
修正前のコード
class SlideFragment(private val imageId: Int): Fragment() { override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? { val view = inflater.inflate(R.layout.fragment_slide, container, false) val ivSlide: ImageView = view.findViewById(R.id.ivSlide) try { ivSlide.setImageResource(imageId) }catch (e: Resources.NotFoundException){ e.printStackTrace() ivSlide.setImageResource(R.drawable.img_error) } return view } }
class MainActivity : AppCompatActivity() { private lateinit var _list: List<Fragment> private lateinit var _adapter: SlidePagerAdapter override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) _list = listOf( SlideFragment(R.drawable.img_slide_1), SlideFragment(R.drawable.img_slide_2), SlideFragment(R.id.ivSlide) //←実験 ) _adapter = SlidePagerAdapter(supportFragmentManager, _list) vpSlide.adapter = _adapter } }