i++

プログラム系のメモ書きなど

Android : android.view.InflateException: Binary XML file line: Error inflating class android.support.design.widget.NavigationView

今回の NavigationView については自分の場合は ProGaurd が原因で、以下の行を proguard-rules.pro に書き加えることで、実行時に表題の InflateException が発生することはなくなりました。

-keep class android.support.v7.widget.LinearLayoutManager { *; }

Issue Tracker によると support library 23.0.1 では発生せず、23.1.0 で発生しているようなので、いずれ修正される案件だとは思われますが、しばらくはこのワークアラウンドが必要そうです。

※ InflateException には他にも原因が考えられるため、原因が ProGuard とは限りません。

参考

Android : Overflow メニューにアイコンを表示する

Overflow メニューに入れられたメニュー項目はそのままではアイコンが表示されませんが、以下の方法でアイコン表示を行うことができました。

f:id:tkyjhr:20151107113525p:plain:w360

コードで実現するバージョン

  • 利点
    • 標準的なメニューの見た目、挙動を保てる
  • 欠点
    • Reflection を使用しているので将来的にも動き続けるかは保障できないと思われる

具体的な方法はこちら
Reflection を使って、メニュー作成時に Menu.setOptionalIconsVisible を呼び出しています。

回答では onMenuOpened() を使っていますが、Fragment のメニューで実現する場合は onCreateOptionsMenu に同じコードを入れて実現できました(featureId のチェックは省略)。コメントには onPrepareOptionsMenu を使っている方がいましたが、onCreateOptionsMenu で一度呼ぶだけで良さそうです。

また、proguard を使う(gradle で minifyEnabled true を指定)と、クラス名及びメンバー名が難読化されてそのままの設定では動かなくなってしまうので、プロジェクト中の proguard-rules.pro に以下の行を追加する必要があります。

-keep class android.support.v7.internal.view.menu.MenuBuilder { void setOptionalIconsVisible(boolean); }

メニュー xml のみで実現するバージョン

  • 利点
    • 簡単にできる
  • 欠点
    • Overflow アイコンを自分で用意する必要がある(標準で使われる Drawable はアプリからは参照できない模様)
    • どのメニュー項目が Overflow メニューに配置されるかを決めておく必要がある(動的に変更できない)

具体的な方法はこちら
Overflow のアイコンと見せかけたメニューを置いて、そのメニューをクリックするとアイコンのあるサブメニューが開くという仕様です。上に挙げた欠点を許容できるのであれば、こちらが安牌のように思います(私はアイコンを設定する必要性がある点が扱いにくく感じたので、コードで行う方法を採用しましたが…)。

参考

Android : Can't bind to local 8600 for debugger 確立された接続がホスト コンピューターのソウトウェアによって中止されました

AndroidStudio でアプリを実機デバッグしようとした際に遭遇しました。

デバイスを抜き差しするだけでは直らず、

  1. デバイスの USBケーブルを抜く
  2. AndroidStudio を終了する
    • Waiting for process detach ダイアログが終わらなかったので強制終了
  3. AndroidStudio を起動する
  4. デバイスを USBケーブルで繋ぐ
  5. デバッグ実行開始

で再びデバッグができるようになりました。
確認できていないので不明ですが、もしかしたら AndroidStudio の再起動だけで良かったのかもしれません。

Kotlin : ビット演算メモ

&| という記号ではなく andor を使います。

また、|=&= といった複合代入演算子形式の記載方法はありません。

Java/C系言語での書き方 Kotlin での書き方
x &= y x = (x and y)
x |= y x = (x or y)
x ^= y x = (x xor y)
~x x.inv()

※ and や or も x.and(y) のように呼び出すことが可能です。

参考

Android : SwipeRefreshLayout の更新中 Indicator を起動時に表示する

onCreateView で直接 SwipeRefreshLayout.setRefreshing(true) をしても表示されないので、post を使って少し遅らせてから refreshing が true になるようにします。

SwipeRefreshLayout の onMeasure が呼ばれて、サイズが確定してから refreshing を true にしないと上手く表示されためだそうです。

mSwipeRefreshLayout.post(new Runnable() {
    @Override
    public void run() {
        mSwipeRefreshLayout.setRefreshing(true);
    }
});

参考

Android : WebView 覚え書き(ズーム関連の機能の有効化、Progress の表示、Back Key のハンドリングなど)

やりたい事 方法
ピンチ操作によるズームイン / ズームアウトの有効化 webview.settings.builtInZoomControls = true
ダブルタップによるズームイン / ズームアウトの有効化 webview.settings.useWideViewPort = true
ズームボタン(右下に表示される±のルーペ)の表示・非表示切り替え webview.settings.displayZoomControls = true/false (ズーム操作不要のページでは true でも表示されません)
プログレスの表示 webview.setWebChromeClient で onProgressChanged を override した WebChromeClient を設定(サンプルコード参照)
バックキーのハンドリング webview.setOnKeyListener で KeyEvent.BACK_KEY のイベントを処理する(サンプルコード参照)
load 時に外部ブラウザを起動しない webview.setWebViewClient(WebViewClient())

※ 方法の列は Kotlin ベースの Property Syntax を使って書いています。Java の場合 webview.getSettings().set***(Boolean) になります

サンプルコード

WebView を持っているレイアウトを使用する Fragment としての例です。
WebView 回りの設定は onCreateView で行っています。

※ こちらも Kotlin です

public class WebViewFragment : Fragment() {
    companion object {
        val ARG_KEY_URL = "ARG_KEY_URL"
        fun newInstance(url:String) : WebViewFragment {
            val fragment = WebViewFragment()
            val args = Bundle()
            args.putString(ARG_KEY_URL, url)
            fragment.arguments = args
            return fragment
        }
    }

    var url:String = ""

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        if(arguments != null) {
            url = arguments.getString(ARG_KEY_URL) ?: ""
        }
    }

    override fun onCreateView(inflater: LayoutInflater?, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        val view = inflater?.inflate(R.layout.fragment_webview_root, container, false)

        val progressbar = view?.findViewById(R.id.fragment_webview_progressbar) as ProgressBar
        val webview = view?.findViewById(R.id.fragment_webview_webview) as WebView

        webview.settings?.builtInZoomControls = true;
        webview.settings?.useWideViewPort = true
        webview.settings?.displayZoomControls = false
        webview.settings?.javaScriptEnabled = true;

        webview.setWebChromeClient(object : WebChromeClient(){
            override fun onProgressChanged(view:WebView, progress:Int) {
                progressbar.progress = progress
                if(progress == 100) {
                    progressbar.animate().setDuration(1000).alpha(0.0f)
                } else {
                    progressbar.alpha = 1.0f
                }
            }
        })

        webview.isFocusableInTouchMode = true
        webview.setOnKeyListener { view, keyCode, keyEvent ->
            if(keyEvent.action == KeyEvent.ACTION_DOWN) {
                if (keyCode == KeyEvent.KEYCODE_BACK) {
                    if (webview.canGoBack() == true) {
                        webview.goBack()
                        return@setOnKeyListener true
                    }
                }
            }
            return@setOnKeyListener false
        }

        webview.setWebViewClient(WebViewClient())
        webview.loadUrl(url)

        return view
    }
}

参考

(本当に stackoverflow 様様だなぁ)

Android : SwipeRefreshLayout で ListView に EmptyView を設定すると ListView が空の時に Refresh 表示が上手くされない問題を解消する

ListView.setEmptyView で Empty View を設定していると、ListView が空の状態で SwipeRefresh (PullToRefresh) を実行すると Refresh の Indicator (上の方でくるくる回る、setRefreshing でコントロールされるもの)が、更新が終わった瞬間辺りに一瞬表示されるだけになるなど、不自然な見た目になってしまいました。

これはどうやら

  • SwipeRefreshView の Refresh 表示は ListView の Visibility に依存している
  • Empty View を設定していると、ListView は空になると自動で非表示にされる

という組み合わせで起こる挙動のようです。

この挙動を「Empty View をセットしても、空の状態で非表示にならない ListView」を作成・使用することで回避します。

カスタム ListView

カスタムといっても、setVisibility を override して Visibility の変更を防ぐ、ただそれだけです。

私の用途では ListView の Visibility を変える予定がない(常に Visible で問題ない)ので、全く何もしないようにしていますが、「空である / 空ではない」以外の要件で Visibility を自分でコントロールする必要がある場合は、参考先(下記記載)のように項目数で if 分岐をつけると良いようです。

※ Kotlin

public class ListViewForSwipeRefreshLayout : ListView {
    public constructor(context: Context) : super(context) {}
    public constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {}
    public constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {}

    override fun setVisibility(visibility: Int) {}
}

layout.xml

ListView を使っているところを、上記のカスタム ListView で置き換えます(パッケージ名は適当です。自分のものに合わせて下さい)。それ以外は SwipeRefreshLayout の標準的な使い方と同じだと思います。

<RelativeLayout
    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">

    <TextView
        android:id="@android:id/empty"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:text="@string/empty"
        android:textAlignment="center"
        android:textColor="@android:color/primary_text_light"
        android:layout_centerInParent="true"/>

    <android.support.v4.widget.SwipeRefreshLayout
        android:id="@+id/swipe_refresh_layout"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
        <com.example.swiperefresh.ListViewForSwipeRefreshLayout 
            android:id="@android:id/list"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:clipToPadding="false">
        </com.example.swiperefresh.ListViewForSwipeRefreshLayout>
    </android.support.v4.widget.SwipeRefreshLayout>

</RelativeLayout>

参考