布団の中にいたい

Androidアプリ開発の勉強をしている傍らで、elasticsearchとかをいじってみたりしています。最近は数学の勉強が楽しくなってきました。

SearchViewの背景色を変更する

SearchViewを導入して、クエリを入力して検索するところまで出来たので、今度はSearchViewの見た目自体を変更しようとしたのですが、かなり詰まったので、その際に調べた内容をまとめます。

SearchViewは他のViewとは微妙に異なっていて、SearchView自体が複数のViewから構成されるViewになっています。具体例で言うと、実際にクエリを入力するEditTextや検索アイコンのImageViewなどが内部で定義されています。なので、アイコンを変えたり背景色を変えたいという場合は、その内部で定義されているViewを変更してあげれば大丈夫です。

今回、SearchViewの背景色を変更したかったので、以下のようにしました。

override fun onCreateOptionsMenu(menu: Menu): Boolean {
    menuInflater.inflate(R.menu.main, menu)

    val menuItem = menu.findItem(R.id.action_search)
    val searchView = MenuItemCompat.getActionView(menuItem) as SearchView

    searchView.findViewById(android.support.v7.appcompat.R.id.search_edit_frame)
            .setBackgroundColor(R.color.white)
   
    // 以下任意の処理
}

これでSearchViewの背景色を変更することができました。他にもsearch_mag_icon(検索アイコン)だったり、search_close_button(☓ボタン)があったりします。SearchViewの何かしらを変更する際は、SearchViewの子要素を色々変更するといいかもしれません。

SearchViewを使用する

GmailFacebookアプリみたいな検索窓を実装したいなーと考え色々調べていると、SearchViewを使用する方法がだいぶ簡単そうだったので、試してみた。

SearchViewのdocumentは以下 SearchView | Android Developers

配置するだけならすごく簡単でToolbarのxmlにSearchViewのitemを配置するだけ。肝心なのは、actionViewClassにSearchViewのクラスを指定してあげることです。

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <item
        android:id="@+id/action_search"
        android:title="Search"
        app:actionViewClass="android.support.v7.widget.SearchView"
        app:showAsAction="always"/>

</menu>

次にSearchViewからのアクションを扱う方法です。今回使用したのは、onQueryTextSubmitです。検索するクエリを入力したあとでsubmitした時に呼ばれるメソッドです。何かを検索する場合、onQueryTextSubmitをoverrideして、内部でAPIを叩く処理を書いたりするいいと思います(間違ってたらごめんなさい)。あとは検索したときのProgressBarを見えるようにするとかかな。

override fun onQueryTextSubmit(query: String?): Boolean {
    // queryがsubmitされたときに期待する動作を記述する
}

WebViewでロード時にProgressBarを表示する

WebViewロード時にProgressBarを表示する WebViewでローディングしているときの画面が真っ白でほんとにローディングしているのか不安になるので,ProgressBarを表示してローディングしているっぽくすることにしました.

WebViewは設定するWebViewClientをいじることである程度の操作を行うことができるみたいです(まだ全容は把握していない)

今回は,ページのローディングの開始時(onPageStarted)と終了時(onPageFinished)の処理をいじることでProgressBarを表示します.

具体的なコードは以下

        webView.setWebViewClient(object : WebViewClient() {
            override fun onPageStarted(view: WebView?, url: String?, favicon: Bitmap?) {
                super.onPageStarted(view, url, favicon)
                progressBar.visibility = View.VISIBLE
            }

            override fun onPageFinished(view: WebView?, url: String?) {
                super.onPageFinished(view, url)
                progressBar.visibility = View.GONE
            }
        })

これでなんとなくローディングしてるっぽい感じになりました!

onKeyDownじゃなくて,onBackPressedでよかったみたい

以前に戻るボタンの動作をhandlingするには,onKeyDownを使うみたいな記事を書いたが,戻るボタンだけでいいなら,onBackPressedをoverrideするだけで良いみたい.

onBackPressedの方がメソッド名が明確でわかりやすいので,戻るボタンだけの用途ならonBackPressedを使ったほうがよさそう.

onKeyDownのときの実装

override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
    // 戻るボタンが押される かつ webviewで前に戻ることができるとき
    if (keyCode == KeyEvent.KEYCODE_BACK && webView.canGoBack()) {
        // 前のページに戻る
        webView.goBack()
        return true
    }

    return super.onKeyDown(keyCode, event)
}

onBackPressedのときの実装

override fun onBackPressed() {
    super.onBackPressed()

    if (webView.canGoBack) {
        webView.goBack()
    }
}

AndroidのToolbarに戻るボタンをつける

Listの要素をタップしたら別のActivityに遷移してWebViewを表示する,みたいな挙動な挙動を実装しようとしているが,前回端末の戻るボタンを押したときにページバックするという実装を行ったせいでどんどんリンク先に進んでいくと前のActivityに戻りづらくなってしまいました.今回はToolbarに戻るボタンを表示して,戻るボタンがタップされたときに,前のActivityに戻るという挙動を実装しました.

やることはかなり単純.そもそもToolbar自体に戻るボタン(Home)を表示するかどうかというメソッドがあるので,それをtrueにして,戻るボタンがタップされたときに,現在のActivityを終了するというだけ.以下はそのコード.だいたい簡略化してますが.

class SampleActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.sample_activity)

        val toolBar = findViewById(R.id.toolBar) as Toolbar
        val webView = findViewById(R.id.webView) as WebView

        setSupportActionBar(toolBar)

        // Toolbarに戻るボタンを表示する
        supportActionBar!!.setDisplayHomeAsUpEnabled(true)
        webView.loadUrl(url)
    }

    override fun onOptionsItemSelected(item: MenuItem?): Boolean {
        when (item!!.itemId) {
            // 戻るボタンが押されたら
            android.R.id.home -> {
                // 現在のActivityを終了する
                finish()
            }
        }

        return super.onOptionsItemSelected(item)
    }
}

WebViewで戻るボタンを押したときに前のページに戻るようにする

AndroidでWebViewを使用して端末の戻るボタンをおした時,前のページに戻るのではなく,前のActivityに戻ってしまう状況になって色々困ったのでメモ.

このような状況で「前のページに戻る」という挙動を実装するには戻るボタンが押されたというイベントをフックする必要がある.Androidでは端末のボタンが押されたときに,onKeyDownというメソッドが呼ばれるので,onKeyDownをoverrideして,戻るボタンが押されたときに前のページに戻るという動作を差し込んでしまえばいい.以下がそのコード.

override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
    // 戻るボタンが押される かつ webviewで前に戻ることができるとき
    if (keyCode == KeyEvent.KEYCODE_BACK && webView.canGoBack()) {
        // 前のページに戻る
        webView.goBack()
        return true
    }

    return super.onKeyDown(keyCode, event)
}

今回は戻るボタンでしたが,他のボタンが押されたときでもKeyEventで指定してしまえばいいので,汎用性はありそうだなーという印象.

AndroidアプリからURLスキームでTwitterアプリに飛ばす

自分で実装中のアプリでTwitterの検索に飛ばす必要があったときに調べた内容です.

やったこととしては以下の2つです.

  • Twitterのアプリがインストールされているかを確認
  • インストールされていればTwitterアプリに,されてなければブラウザに飛ばす.

Twitterのアプリがインストールされているかを確認

アプリがインストールされているかはPackageManagerのgetPackInfoで調べられます.

PackageManager | Android Developers

class Utils {
    companion object {
        fun isApplicationInstalled(context: Context, uri: String): Boolean {
            val pm = context.packageManager

            try {
                pm.getPackageInfo(uri, PackageManager.GET_ACTIVITIES)
                return true
            } catch (e: PackageManager.NameNotFoundException) {
                return false
            }
        }
    }
}

getPackageInfoで指定されたアプリがなければ,PackageManager.NameNotFoundExceptionが投げられるので,そこでfalseを入れてreturnしてあげればアプリの有無を判定できます.ちなみにTwitterのpackageはcom.twitter.androidです.google playのアプリページのurlのquery stringのidパラメータを見ればわかります.

インストールされていればTwitterアプリに,されてなければブラウザに飛ばす

アプリの存在判定ができたので,後はそれで分岐させてインストールされていればアプリに,なければブラウザに飛ばすだけです.

画面遷移は普通にintentで実現できます.今回の場合,遷移先のActivityが明示的ではないので,俗に言う暗黙的intentってやつになります.暗黙的intentの場合,intentの第一引数にActionに設定します.Actionはデータ表示や電話をかけたりなど幾つかのActionがあるみたいです.今回の場合,どちらにしろデータ表示するだけなので,Intent.ACTION_VIEWを使用します.今回は以下みたいな感じでやりました.

val intent = Intent(Intent.ACTION_VIEW, uri)
startActivity(intent)

まとめ

まだAndroidの勉強を初めて間もないですが,割りと使用していたのはActivity間の遷移だったので,今回のような暗黙的intentみたいなのはなかなかよく分からず大変でした.こういう知見を少しずつ貯めていきたいと思います.