【Android】~ちょこメモ~ 画面の向きを固定してみた
画面の向きを固定したい
デフォルトの動作では、
ユーザーが画面の向きを変えることができる
ようになっています(∩´∀`)∩
でも、場合によっては「横画面にしてほしくないなぁ」なんて時もあるかと思いますので、
設定方法について解説します!
マニフェストに追記
やることは簡単('ω')ノ
AndroidManifest.xmlに追記するだけです!
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.fixedscreensample"> <application ~省略~> <activity android:name=".MainActivity" android:screenOrientation="portrait"> <!-- ↑これを追加↑ --> <intent-filter> <!-- ~省略~ --> </intent-filter> </activity> <activity android:name=".SubActivity"></activity> </application> </manifest>
固定したいActivityの属性に「android:screenOrientation」を追加すればOKです!
指定できる値はたくさんありますが、ココでは3つに絞って紹介します(・ω・)ノ
unspecified | (デフォルト値)システムが画面の向きを選択します。指定していない場合はこれになります。 |
---|---|
portrait | 縦方向で固定されます。※サンプルではこれ |
landscape | 横方法で固定されます。 |
そのほかの属性値が知りたい方はこちら↓
developer.android.com
注意点
注意しなければいけないのが、
この属性は「アクティビティごと」に設定しなければいけないということです!
例えば上記のサンプルでは、
MainActivityを表示中は画面が固定されますが、
SubActivityを表示中は画面が固定されません。
もしSubActivityでも画面を固定したい場合は
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.fixedscreensample"> <application ~省略~> <activity android:name=".MainActivity" android:screenOrientation="portrait"> <intent-filter> <!-- ~省略~ --> </intent-filter> </activity> <activity android:name=".SubActivity" android:screenOrientation="portrait"></activity> <!-- ↑これを追加↑ --> </application> </manifest>
こうすればOKです(/・ω・)/
まとめ
今回は「画面の固定方法」についての記事でした。
Activityは画面の向きを変えると
onDestroy()が呼ばれて再生成されてデータ飛ぶし、
横向きにするとレイアウト崩れるし・・・って時には便利かもしれません。
ただ、特に理由がないときには
ちゃんと横向き用のレイアウト用意して、データもsavedInstanceStateで復元して・・・ってやった方が、
ユーザーにとってもアプリにとってもいいのかなと、ちょっと思いました。。。(;´・ω・)
まぁ時と場合によりますかね!笑
ってことで以上です。おわり!
【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 } }
【Android】~ちょこメモ~ダイアログ「画面外タップで閉じる」操作を無効化する
はじめに
今回の「ちょこっとメモ」、
自分の語彙力だとタイトルがどうしても長くなってしまうので
断腸の思いで「ちょこメモ」に改名しました…(´;ω;`)笑
ダイアログの実装方法について知りたい方は、
こちらの記事をご覧ください
ukit-labo.hateblo.jp
調べたきっかけは、今現在開発中の個人アプリで
「”OK”か”NG”か、必ずどちらかを選択するダイアログ」
を作成したい場面が出てきた時でした。
まずは、場面を想像しやすいように
サンプルコードを紹介します。
サンプルコード
今回題材として使用するサンプルコードです。
activity_main.xml
まずは、アクティビティです。
ボタンのみです。YES。
<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"> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:onClick="onDialogButtonClick" android:text="@string/btn_dialog" app:layout_constraintTop_toTopOf="parent" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent"/> </androidx.constraintlayout.widget.ConstraintLayout>
SampleDialogFragment.kt
次に、「ダイアログ」を表すクラスです。
どちらのボタンが押されたかをトーストで表示する、という
極限までシンプルなダイアログにしました。
class SampleDialogFragment: DialogFragment() { override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { val builder = AlertDialog.Builder(activity) builder.setTitle("サンプル") builder.setPositiveButton("OK"){_, _ -> Toast.makeText(context, "OKボタンが押されました", Toast.LENGTH_LONG).show() } builder.setNegativeButton("NG"){_, _ -> Toast.makeText(context, "NGボタンが押されました", Toast.LENGTH_LONG).show() } return builder.create() } }
MainActivity.kt
最後にアクティビティクラスです。
ボタンを押すとダイアログを表示するプログラム
だけですね笑
超シンプル。
class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) } fun onDialogButtonClick(view: View){ val dialog = SampleDialogFragment() dialog.show(supportFragmentManager, "SampleDialogFragment") } }
問題点
私がこのダイアログに求める挙動は
「”OK”か”NG”、どちらかを必ず選択する」、
要するに
「キャンセル操作を無効化する」
というものでした。
しかし現在(デフォルト)の挙動では、
ダイアログ画面外をタップすると、ダイアログが閉じてしまいます。
これは、ダイアログ生成時にsetNeutralButton()をして
「キャンセルボタン」を作っていなくても、
「キャンセルは可能」
ということになります。
これじゃあ思った通りの実装ができないめぅ(;_;)
ってことです(?)
解決策
ダイアログフラグメントに対して
「isCancelable() = false」とします。
isCancelable()メソッドは
「ダイアログのキャンセル操作の有効・無効」をboolean値で設定します。
①ダイアログクラスに記述するパターン
class SampleDialogFragment: DialogFragment() { override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { val builder = AlertDialog.Builder(activity) builder.setTitle("サンプル") builder.setPositiveButton("OK"){_, _ -> Toast.makeText(context, "OKボタンが押されました", Toast.LENGTH_LONG).show() } builder.setNegativeButton("NG"){_, _ -> Toast.makeText(context, "NGボタンが押されました", Toast.LENGTH_LONG).show() } //↓↓これを追加する。 this.isCancelable = false return builder.create() } }
もしくは、
②ダイアログを呼び出した側に記述するパターン
class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) } fun onDialogButtonClick(view: View){ val dialog = SampleDialogFragment() //↓↓これを追加する dialog.isCancelable = false dialog.show(supportFragmentManager, "SampleDialogFragment") } }
ポイント
まず
「①ダイアログクラスに追加するパターン」について
記述場所は
- onCreateDialog()の中
- return の前
そして、
- 「this」に対して
isCalcelable()を呼び出していることがポイントです。
この場合の「this」は”DialogFragment”であり、
生成する"AlertDialog"または"AlertDialog.Builder"ではありません。
次に
「②ダイアログを呼び出した側に記述するパターン」です。
生成したSampleDialogFragmentのインスタンスに対して、isCalcelable()を設定しています。
”SampleDialogFragment”は”DialogFragment”を継承しているため、
タイミングが違うだけで
やっていることは①と同じですね。
使い分けとしては、
ダイアログクラスから同じダイアログしか生成しない場合は
①のパターン。
ダイアログクラスを使いまわして使用する場合、キャンセル操作を個々で設定したい場合などは
②のパターン。
ってな感じで使えばいいんじゃないでしょうか。
注意点
この操作では、
生成したダイアログのすべてのキャンセル操作を無効にします。
つまり、
「”戻るボタン”によるキャンセル操作」
も無効になってしますの注意しましょう!
【Android Studio】ダイアログを使ってみるぞい
はじめに
アプリを作っていく中で、ダイアログを使用する機会がありました。
実装しながら調べていると
なかなか応用の利く便利な仕組みになっているようなので、
調べた内容を自分の考察や応用も含めてまとめていこうと思います(^-^)
ダイアログを使うメリット
まず、
ダイアログを使用するメリットについて、自分なりに考えてみました。
メリットを見つけるために、ダイアログの特徴を考えてみます(∩´∀`)∩
まず、ダイアログの使い方として、
「アクティビティでユーザーが行った操作の確認」
というのが一般的で、イメージが浮かびやすい使い方なのではないでしょうか。
もちろん確認画面として使用できることも特徴ではあると思うのですが、
やり方によっては、入力画面として使用するといった使い方も可能です。
これを踏まえるとダイアログの特徴は、
「遷移ではなく最前面に追加表示させる小さな画面」
ということになります。
例えば入力内容の確認のためだけに画面を遷移させる場合、
画面が毎度切り替わることでユーザに”操作画面の多さ”を感じさせてしまい、
ユーザビリティが低下してしまいます。
ダイアログを使用することで、元の画面を見せた状態で小さな画面がポップアップするため、実際の操作は変わらなくとも”操作画面の多さ”をあまり感じさせないようになっています。
また、この仕組みって「フラグメント」と似ていますよね。
なぜでしょう?
実はandroidのダイアログは「フラグメント」の一種だからです。
そのため
「フラグメントとしてのメリットも持ちつつ、ダイアログとしての機能を簡単に実装できるメソッドがたくさん用意されている」
ことがダイアログの大きなメリットではないかな、と思います。
さぁ、では実際に実装していきましょう!
確認ダイアログを実装してみる
【実装内容】
入力テキストを表示欄に表示させるかを確認。
”はい”の場合:入力された文字列を表示欄に表示する
”いいえ”の場合:キャンセルし、それをトーストで通知する
使用するレイアウト
<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:gravity="end" android:orientation="vertical" tools:context=".MainActivity"> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="5dp" android:text="ダイアログの実装" android:textSize="25sp" android:textStyle="bold" /> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="10dp" android:background="@color/colorPrimary" android:padding="5dp" android:text="入力内容の表示" android:textColor="#ffffff" android:textSize="20sp" /> <TextView android:id="@+id/tvOutput" android:layout_width="match_parent" android:layout_height="wrap_content" android:ellipsize="end" android:maxLines="1" android:textSize="20sp" /> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="10dp" android:background="@color/colorPrimary" android:padding="5dp" android:text="文字を入力" android:textColor="#ffffff" android:textSize="20sp" /> <EditText android:id="@+id/etInput" android:layout_width="match_parent" android:layout_height="wrap_content" android:hint="ここに入力" android:inputType="text" android:maxLines="1" /> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:onClick="onConfirmButtonClick" android:text="確認" /> </LinearLayout>
①ダイアログを表すクラスを作成する
まず、
ダイアログとして使用する
ダイアログフラグメントクラスを作成していきます。
ダイアログとして使用するためには、
DialogFragmentクラスを継承しておく必要があります。
コンストラクタの引数には何も渡しません。
class ConfirmDialogFragment : DialogFragment(){
}
②内部にリスナインタフェースを定義する
今回の例のように、
ダイアログのボタン選択によって呼び出し元のアクティビティでの動きを変える場合、
そのような処理を記述しなければいけませんΣ(゚Д゚)
これを実現する方法はいくつかありますが、今回は
「ボタンが押された時の処理をアクティビティクラス側に記述する」
方法を使っていきます。この方法のメリットは、アクティビティ側のデータをダイアログ側へ持って行かなくてもいいことです。
具体的な処理をアクティビティ側に委ねることでダイアログクラス内の記述が減り、
また、機能が分離されることで他の場面でも使いまわすことが容易になりますね(/・ω・)/
そのためにまず、
「具体的な処理の枠組み」となるインタフェースを定義します(^^)/
また、
リスナインタフェースを実装したアクティビティのインスタンスを
ダイアログフラグメント側で保持するための変数も定義しています。(^-^)
class ConfirmDialogFragment : DialogFragment(){ /* ConfirmDialogListenerを実装したアクティビティのインスタンスを、 * ConfirmDialogListener型にキャストしたものがここに入る */ private lateinit var listener: ConfirmDialogListener interface ConfirmDialogListener{ //「はい」ボタンが押された時用のメソッド fun onDialogPositiveClick() //「いいえ」ボタンが押された時用のメソッド fun onDialogNegativeClick() } }
③アクティビティクラスに、リスナインタフェースを実装
アクティビティクラスにリスナインタフェースを実装することで、
各ボタンが押された時の処理をこちら側に記述することができます。
今回は例として
〈「はい」が選択された場合〉
入力欄の文字列を、表示欄に表示させる
〈「いいえ」が選択された場合〉
キャンセルされたことをトースト表示する
という内容を実装しました。
class MainActivity : AppCompatActivity(), ConfirmDialogFragment.ConfirmDialogListener { override fun onCreate(savedInstanceState: Bundle?) { // 省略 } // "はい"が押された時 override fun onDialogPositiveClick() { val inputStr = etInput.text.toString() tvOutput.text = inputStr } // "いいえ"が押された時 override fun onDialogNegativeClick() { Toast.makeText(this, "キャンセルされました",Toast.LENGTH_SHORT).show() } }
④ダイアログフラグメント側で、初期化処理を行う
onAttach()メソッドをオーバーライドして、
listener変数を初期化します(/・ω・)/
その際、try-catchを使用して、
アクティビティクラスがConfirmDialogListenerを
「実装していればキャストして保持」、
「実装していなければ例外再スロー」
という処理にしています(*^^)
class ConfirmDialogFragment : DialogFragment(){ /* * 省略 */ override fun onAttach(context: Context) { super.onAttach(context) try { listener = context as ConfirmDialogListener }catch (e: ClassCastException){ throw ClassCastException("$context must be implement ConfirmDialogListener") } } }
⑤ダイアログをセットアップする
やっとダイアログの設定を行っていきます(;´∀`)
AlertDialog.Builderを使って、タイトルやメッセージの設定を行います。
また、set~Button()でボタンの設定も行っています。
書き方をかなり簡略化していますが、
コメントアウトした部分と同じ意味になります。
class ConfirmDialogFragment : DialogFragment(){ private lateinit var listener: ConfirmDialogListener /* * 省略 */ override fun onAttach(context: Context) { /* * 省略 */ } override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { return activity?.let{ val builder = AlertDialog.Builder(it) builder.setTitle("入力内容の確認") builder.setMessage("入力内容を表示しますか?") builder.setPositiveButton("はい"){ _, _ -> listener.onDialogPositiveClick() } /* builder.setPositiveButton( "はい", DialogInterface.OnClickListener{dialog, which -> listener.onDialogPositiveClick() }) */ builder.setNegativeButton("いいえ"){ _, _ -> listener.onDialogNegativeClick() } builder.create() } ?: throw IllegalStateException("Activity cannot be null") } }
⑥ダイアログを表示させる処理を記述する
アクティビティ側で、ダイアログを表示させる処理を記述していきます。
とっても簡単です。
適切なタイミングでダイアログクラスをインスタンス化し、show()を呼び出すだけです。
show()の第1引数には「supportFragmentManager」を指定します。
第2引数は、表示させるダイアログフラグメントクラスを表す文字列です。
class MainActivity : AppCompatActivity(), ConfirmDialogFragment.ConfirmDialogListener { override fun onCreate(savedInstanceState: Bundle?) { // 省略 } fun onConfirmButtonClick(view: View) { val dialog = ConfirmDialogFragment() dialog.show(supportFragmentManager, "ConfirmDialogFragment") } override fun onDialogPositiveClick() { // 省略 } override fun onDialogNegativeClick() { // 省略 } }
完成
できました(^_^)v
今回は「確認ダイアログ」として実装しましたが、前述のとおり
「入力用ダイアログ」としても使うことができます。
また、それについてはまとめて記事にしたいと思います<(_ _)>
【Android Studio】スクロールしたら隠れるアクションバーを実装してみた
初めに
今回は折りたたみ式のアプリバーをつくります!
”折り畳み式”というのは、こういうことです↓
画面のスクロールに合わせて、アクションバーが見え隠れしていますね(^-^)
これは、前回の記事にもあった便利な「ツールバー」を使って実装できるんです(*^^)v
ツールバーって何?
という方は以下のリンクをご覧ください(/・ω・)/
ukit-labo.hateblo.jp
では、やってみましょう!
スクロールさせるコンテンツを用意
まず、スクロールさせるコンテンツを適当に作ります。
※後でインクルードするので別ファイルで作成します
<androidx.core.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent" app:layout_behavior="@string/appbar_scrolling_view_behavior"> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/tv_waganeko" /> </androidx.core.widget.NestedScrollView>
ScrollViewではなくNestedScrollViewを使わないと、うまく画面に入らなかったり
いろいろと問題が出ます。
また、属性に
app:layout_behavior="@string/appbar_scrolling_view_behavior"
を指定しておきます。
実装パターン①
<androidx.coordinatorlayout.widget.CoordinatorLayout 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"> <com.google.android.material.appbar.AppBarLayout android:layout_width="match_parent" android:layout_height="wrap_content"> <androidx.appcompat.widget.Toolbar android:id="@+id/toolbar" android:layout_width="match_parent" android:layout_height="?attr/actionBarSize" app:logo="@mipmap/ic_launcher" app:layout_scrollFlags="scroll|enterAlways"/> </com.google.android.material.appbar.AppBarLayout> <include layout="@layout/content_main"/> </androidx.coordinatorlayout.widget.CoordinatorLayout>
class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) setSupportActionBar(toolbar) } }
※ツールバーを使用するために「アクションバーの非表示」を以降すべてのコードで行っているとします
ポイント①:CoordinatorLayout
メインアクティビティではルートに「CoordinatorLayout」を使用しています。
このレイアウトは”画面部品同士の動きを調整する”という特徴があり、
子となるViewの位置や大きさなどを動的に管理できます。
今回のように、
親子関係のない画面部品同士に連動した動き*1をさせる場合は、全体をCoordinatorLayoutで囲う必要があります。
ポイント②:AppBarLayout
表示するツールバーの親要素として「AppBarLayout」を使っています。
これはアクションバー部分を連動させるための画面部品です(^-^)
AppBarLayoutはLinearLayoutを継承しており、オリエンテーションは縦になっています。
そのため、このレイアウト内の要素は縦に並べられ、それらがアクションバーの位置に配置されます。
子要素に「layout_scrollFlags」属性を付与することで、その要素をスクロールに連動させることができます。
ポイント③:Toolbar
AppBarLayoutの子要素として定義しています。
今回はこのツールバーをスクロール連動させたいので、
「layout_scrollFlags」属性を指定します。
まずscrollと記述し、「|」で区切った後スクロールモードを指定します
スクロールモード | 上にスクロールした時 | 下にスクロールした時 |
---|---|---|
enterAlways | ツールバーがすぐに消える | ツールバーがすぐに出てくる |
この時注目なのは、「下にスクロールした時の動き」です。
コンテンツの上端を意識しない動きになっています。(↓こんな感じ)
・下にスクロールした途端にツールバーがニョキっと出てくる
・さらにスクロールするとツールバーが表示されたままコンテンツがスクロール
・コンテンツの上端に到達
実装パターン②
<androidx.coordinatorlayout.widget.CoordinatorLayout 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"> <com.google.android.material.appbar.AppBarLayout android:layout_width="match_parent" android:layout_height="200dp"> <com.google.android.material.appbar.CollapsingToolbarLayout android:id="@+id/toolbarLayout" android:layout_width="match_parent" android:layout_height="match_parent" app:layout_scrollFlags="scroll|enterAlwaysCollapsed"> <androidx.appcompat.widget.Toolbar android:id="@+id/toolbar" android:layout_width="match_parent" android:layout_height="?attr/actionBarSize" app:layout_collapseMode="pin" app:logo="@mipmap/ic_launcher" /> </com.google.android.material.appbar.CollapsingToolbarLayout> </com.google.android.material.appbar.AppBarLayout> <include layout="@layout/content_main" /> </androidx.coordinatorlayout.widget.CoordinatorLayout>
class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) setSupportActionBar(toolbar) } }
ポイント①:CollapsingToolbarLayout
AppBarLayoutの子要素、Toolbarの親要素として
CollapsingToolbarLayoutを指定しています。
これはスクロール時に連動させてAppBarLayoutのサイズを変更できるレイアウト部品です。
今回はサイズ変更がわかりやすいようにAppBarLayoutの高さを固定値として設定してみました。
サイズを変更させるのはToolbarではなくCollapsingToolbarLayoutの役割です。
そのため、先ほどはToolbarに記述していたスクロールモードの設定を、
今回はここに記述する必要があります。
スクロールモード | 上にスクロールした時 | 下にスクロールした時 |
---|---|---|
enterAlwaysCollapsed | ツールバーがすぐに消える | スクロールの上端まで行ったときにツールバーが表示 |
ポイント②:Toolbar
Toolbarで「layout_collapseMode」の属性値に"pin"を指定をすると、
Toolbarは常にアクションバーの位置にとどまります。
これで上にスクロールした時に、アイコンの位置までタイトルが移動して、一緒に隠れる動きになります。
指定しないと、上にスクロールした時すぐにアイコンだけが隠れてしまいます。
実装パターン③
<androidx.coordinatorlayout.widget.CoordinatorLayout 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"> <!-- 省略 --> <com.google.android.material.appbar.CollapsingToolbarLayout android:id="@+id/toolbarLayout" android:layout_width="match_parent" android:layout_height="match_parent" app:layout_scrollFlags="scroll|enterAlwaysCollapsed"> <androidx.appcompat.widget.Toolbar android:id="@+id/toolbar" android:layout_width="match_parent" android:layout_height="?attr/actionBarSize" app:layout_collapseMode="pin" app:logo="@mipmap/ic_launcher" /> <!-- 省略 --> </androidx.coordinatorlayout.widget.CoordinatorLayout>
このパターンはCollapsingToolbarLayoutのスクロールモードを変更しただけです。
スクロールモード | 上にスクロールした時 | 下にスクロールした時 |
---|---|---|
exitUntilCollapsed | ツールバーがすぐに消える(一部残る) | スクロールの上端まで行ったときにツールバーが表示 |
先ほどと違い、どれだけ上にスクロールしても、縮小したツールバーが完全に隠れません。
これには、Toolbarに「app:layout_collapseMode="pin"」の指定が必要です。
まとめ
いろいろと調べていると、アプリバーに画像を指定したり、FABをおしゃれに追加したりと
アイデア次第で幅広い実装が可能なようですΣ(゚Д゚)
また気になることがあったら記事にしますね~(^_^)vℚ
【Android Studio】Toolbarを使って柔軟なアプリバーを作る
アプリバーってなんじゃ
アプリを開いた時の上に表示されるバーの、ステータスバーじゃない方(?)。
下の画像の「下側の薄い緑の部分」がそうです↓一般的に「アクションバー」と言われることが多い気がしますが、この記事では後述のActionBarとごっちゃになりそうなので「アプリバー」として説明することにします。
このアプリバーを表示させるために、AndroidStudioでは2通りの方法があります
ActionBar
デフォルトでアプリバーとして実装されています。
styles.xmlファイルにて、ActionBarの詳しい設定が可能です。
自動作成されたコードを見てみましょう。簡単に内容を解説します。
- styleタグのname属性に"AppTheme"を指定することで、アプリのテーマとして認識されている。
- parent属性にAndroidSDKで用意されているテーマの一つ(Light.DarkActionBar)を親として指定、必要な部分だけを書き換えている。
- 子要素として「アクションバーの色」「ステータスバーの色」「アクセントカラー」を設定している。
ActionBarはデフォルトで実装されているので楽なのは楽ですが、
アプリバーに色々な動きをつけたりできないということ、
また、様々な Android リリースを通じて徐々に機能が追加されているため、使用する Android システムのバージョンやデバイスによって動作が異なる可能性があること
これらの問題があるようです。
Toolbar
Toolbarは、ActionBarの代わりにアプリバーとして表示することができる機能です。
アプリバーをスクロールに追従させて広げたり、アクティビティによって表示&非表示を切り替えたり、カスタマイズ性の高い実装が可能になります。
またToolbar のサポートライブラリを使用できるすべてのデバイスでこれらの機能を使用できるというメリットもあります。
ただし、Toolbarの実装にはいくつかの手順を踏まなければいけません。
今回はこれを紹介していきます!
Toolbar実装方法
実装には大きく分けて3つのことを行います。
①アクションバーを非表示にする
ToolbarをActionBarの代わりにするので、ActionBarの方を非表示にしないといけません。
これをしないと、こうなります↓
ーーやり方ーー
styles.xml、AppThemeスタイルの”parent”属性を変更する
変更前↓
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar"> <!-- 省略 --> </style>
変更後↓
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar"> <!-- 省略 --> </style>
parentに”~.NoActionBar”を指定することで、ActionBarが非表示になります
※ほかにも「Theme.AppCompat.NoActionBar」と「Theme.AppCompat.DayNight.NoActionBar」がありますが、どれもActionBarは非表示になります。
②レイアウトファイルにToolbarを追加
次に、レイアウトにToolbarを追加します。
ここに追加するということは、
ビューのようにサイズや動きなどを動的に付けられるということですね。
ActionBarはレイアウトファイルには記述できないので、ここがToolbarの長所です。
<androidx.appcompat.widget.Toolbar android:id="@+id/toolbar" android:layout_width="match_parent" android:layout_height="?attr/actionBarSize" android:background="@color/colorPrimary" android:elevation="4dp" app:logo="@mipmap/ic_launcher" app:title="ツールバーやで" app:titleTextColor="#ffffff" app:subtitle="サブタイトルやで" app:subtitleTextColor="ffffff"/>
[IDの指定]
アクティビティクラスから呼び出せるように、IDの指定をします。
[高さ(height)の指定]
”?attr/actionBarSize”として、
非表示になっているアクションバーの高さに合わせています
(たぶんそういうことだと思います)
[影(elevation)の設定]
影の大きさを決めます
ActionBarではデフォルトで「4dp」となっているようです。
今回はこれに合わせて設定しました
[表示内容の設定]
「app:~」でロゴ、タイトル、サブタイトルの指定をしています。
これでツールバーの表示はできました、
しかし、このままではToolbarは「レイアウトの一部」としてしか認識されていません。
今回はアプリバーとして使用するので、そのようにアクティビティクラスに追記していきます。
③アプリバーとして設定
これは簡単です。
アクティビティクラスのonCreate()に一行追加します。
class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) //↓これを追加 setSupportActionBar(toolbar) } }
見た目は変わりませんが、
これでプログラムに「Toolbarはアプリバーとして使うよ」
ということが認識されました
ツールバーを動的にカスタマイズする
では、ツールバーを使うことの最大のメリットである、動的カスタマイズをやってみましょう!
アクティビティクラスのonCreate()内で、初期値を変更していきます。
これらの設定や変更を行ったら「setSupportActionBar()」で変更を反映させます。
class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) //ロゴを削除する toolbar.logo = null //タイトル文字列と文字色を変更する toolbar.title = "こけこっこ" toolbar.setTitleTextColor(Color.BLACK) //サブタイトル文字列を文字色を変更 toolbar.subtitle = "はとぽっぽ" toolbar.setSubtitleTextColor(Color.RED) //変更を反映 setSupportActionBar(toolbar) } }
ただこれはものすごい無駄なコードですね。('ω')
onCreate()で変更するなら、レイアウトファイルで初期値を変えておけばいいです。
ボタンクリックで表示が変わる、などの処理なら
そのリスナに変更処理を書くようにします。
まとめ
アクションバーの代わりにツールバーを使うことで、
ほかにもいろいろな機能を実装できるようです。
ビューのようにカスタマイズできるのはめっちゃ便利ですね~(^_^)v
ってことでこれからはこれを有効活用していこうと思います!
【Android Studio】Applicationクラスのメソッドが呼ばれるタイミングを検証してみた
- はじめに
- アプリを起動する
- アプリを終了する
- スリープ状態にする
- スリープを解除する
- タスクビューにする、ホーム画面に戻る
- 画面を遷移する
- 遷移先から戻る
- 遷移後にアプリを強制終了してみる
- 遷移後に電源を切ってみる
- MainActivity表示中に画面を回転させてみる
- 遷移後に画面を回転させてみる
- 遷移後に画面を回転させ、そのままMainActivityに戻ってみる
- 遷移後に画面を回転させ、また戻して、それからMainActivityに戻ってみる
- まとめ
はじめに
「Activityのコールバックメソッドの呼ばれ方」については、
こちらをご覧ください!
ukit-labo.hateblo.jp
前回は、Activityのコールバックメソッドの呼ばれ方を検証しました!
では、Applicationクラスのメソッドはどうでしょう?
実際にアプリを動かしながらどのようにして各メソッドが呼ばれているのかを確認してみます。
各メソッドにログ出力を実装して、前回と同じパターンで検証してみます!
また、内容が重複するといけないので、
Activityのコールバックメソッドについては無視してあります。
Applicationクラスのメソッドが呼ばれた時にだけ、順序がわかりやすいように一緒に載せています。
さぁ、やっていきましょう!
アプリを起動する
【検証】
[ホーム画面] → [アプリ起動]
【結果】
①App_onCreate()
②onCreate()
③onStart()
④onResume()
~アプリ起動~
アプリを終了する
【検証】
[アプリ起動] → [アプリ終了]
【結果】
呼び出されない
スリープ状態にする
【検証】
[アプリ起動] → [スリープ]
【結果】
呼び出されない
スリープを解除する
【検証】
[アプリ起動] → [スリープ] → [スリープ解除]
【結果】
呼び出されない
タスクビューにする、ホーム画面に戻る
【結果】
呼び出されない
画面を遷移する
【検証】
[Main] → [Sub]
【結果】
呼び出されない
遷移先から戻る
【検証】
[Main] → [Sub] → [Main]
【結果】
呼び出されない
遷移後にアプリを強制終了してみる
【検証】
[Main] → [Sub] → [アプリ終了]
【結果】
呼び出されない
遷移後に電源を切ってみる
【検証】
[Main] → [Sub] → [電源OFF]
【結果】
呼び出されない
MainActivity表示中に画面を回転させてみる
【検証】
[Main] → [横画面にする]
【結果】
①App_onConfigurationChanged()
②Main_onPause()
③Main_onStop()
④Main_onDestroy()
⑤Main_onCreate()
⑥Main_onStart()
⑦Main_onResume()
~画面回転~
遷移後に画面を回転させてみる
【検証】
[Main] → [Sub] → [横画面にする]
【結果】
①App_onConfigurationChanged()
②Sub_onPause()
③Sub_onStop()
④Sub_onDestroy()
⑤Sub_onCreate()
⑥Sub_onStart()
⑦Sub_onResume()
~画面回転~
遷移後に画面を回転させ、そのままMainActivityに戻ってみる
【検証】
[Sub] → [横画面にする] → [Main]
【結果】
呼び出されない
※画面回転時には「App_onConfigurationChanged()」が呼び出されています
遷移後に画面を回転させ、また戻して、それからMainActivityに戻ってみる
【検証】
[Sub] → [横画面にする] → [縦画面にする] → [Main]
【結果】
呼び出されない
※画面回転時には「App_onConfigurationChanged()」が呼び出されています
まとめ
今回の検証は以上です!
Activityと違い、呼び出される個所がかなり限定されていますね!
まとめると、こんな感じでしょうか。
onCreate()はアプリ起動時に呼び出されていました。
また、画面が回転した時Activityは再起動されていましたが、Applicationではされないようです。
onConfigrationChanged()は、画面が回転した時、真っ先に呼び出されていました。
これは「コンポーネントの実行中にデバイス構成が変更される」と呼ばれるそうです。
”画面が回転する”というのもその条件の一つのようですね。
Activityでは、遷移後のアクティビティから戻る際の”画面の向き”によって処理が変わっていましたね(^_^)v
しかしApplicationでは、戻ってきた時の画面の向きによる呼び出し方の違いはありませんでした。
また、Applicationではアプリ終了時に呼び出されるメソッドはありません。
「onTerminate()」というものがありますが、これはエミュレータ上で呼ばれるメソッドだそうで、
実機では呼ばれることがないようなので注意です(/・ω・)/
※私は実機を使ってテストをしているため、このメソッドは呼ばれませんでした。