布団の中にいたい

Elasticsearchいじったり、Androidアプリ書いたり。最近は数学の勉強が楽しくなってきました。

spark(web framework)で遊んでみようとしたら最初の実行で怒られた

sparkで簡単なWebアプリを作ってみようと思ったのですが、詰まったのでメモ。

詰まったのは起動方法です。 ただHello Worldをさせようと思ったのですが、そこで詰まりました。 エラーは以下です。

SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.

調べてみると、gradleに記述したライブラリでslf4jを使用しているものがあり、slf4jをインストールする必要があるみたいです。 加えて、slf4jの設定ファイルをsrc/main/resourcesに追加する必要があります。

以下の記事を参考にさせていただきました。

blog.stormcat.io

onBackPressedの落とし穴

onKeyDownをonBackPressedに切り替えて使ってみたら、問答無用で遷移前のActivityに戻されてしまう現象に遭遇しました。正直中途半端な知識で使うのはどうかと思っていたので、内部でどのような動作をしているか少し調べてみました。

ソースコードをどんどん辿っていったところ、onBackPressedが定義されているのは、Activity.javaの内部です。同様にonKeyDownもActivity.javaにありました。

実装されていたonBackPressedは以下です。

コメントを見るだけで何やら不穏な空気が漂っていますね。とりあえず簡単な実装として、何かするためにonBackPressedをoverrideできるときは現在のActivityを終了しますとのこと。

    /**
     * Called when the activity has detected the user's press of the back
     * key.  The default implementation simply finishes the current activity,
     * but you can override this to do whatever you want.
     */
    public void onBackPressed() {
        if (mActionBar != null && mActionBar.collapseActionView()) {
            return;
        }

        if (!mFragments.getFragmentManager().popBackStackImmediate()) {
            finishAfterTransition();
        }
    }

終了処理は、finishAfterTransitionっぽいですね。Fragmentのスタックから何も取り出せないとき(addToBackStackとかしてない)にこの処理に入ります。次にfinishAfterTransitionを見てみます。

ActivityTransitionStateはActivityの遷移状況を管理しているんですかね。ソース眺めてみましたが、他のものと違って色々知識が必要なので、メソッド名の字面から判断します。

    /**
     * Reverses the Activity Scene entry Transition and triggers the calling Activity
     * to reverse its exit Transition. When the exit Transition completes,
     * {@link #finish()} is called. If no entry Transition was used, finish() is called
     * immediately and the Activity exit Transition is run.
     * @see android.app.ActivityOptions#makeSceneTransitionAnimation(Activity, android.util.Pair[])
     */
    public void finishAfterTransition() {
        if (!mActivityTransitionState.startExitBackTransition(this)) {
            finish();
        }
    }

すべてを理解しきれたわけではありませんが、onBackPressedの行き着く先にはfinishがいることがわかりました。ちらっとonKeyDownも眺めてみましたが、実のところ、内部でonBackPressedを呼んでいたので使う際は注意が必要かもしれません。

PS Android Studioのどんどんメソッドを辿っていける機能、ソース読むときにかなり便利だということに気づきました。

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)
    }
}