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 メニューに入れられたメニュー項目はそのままではアイコンが表示されませんが、以下の方法でアイコン表示を行うことができました。
コードで実現するバージョン
- 利点
- 標準的なメニューの見た目、挙動を保てる
- 欠点
- 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 はアプリからは参照できない模様)
- Material icons - Google Design の more_vert アイコンが標準のものに近い(縦の3点並び)です
- どのメニュー項目が Overflow メニューに配置されるかを決めておく必要がある(動的に変更できない)
- Overflow アイコンを自分で用意する必要がある(標準で使われる Drawable はアプリからは参照できない模様)
具体的な方法はこちら。
Overflow のアイコンと見せかけたメニューを置いて、そのメニューをクリックするとアイコンのあるサブメニューが開くという仕様です。上に挙げた欠点を許容できるのであれば、こちらが安牌のように思います(私はアイコンを設定する必要性がある点が扱いにくく感じたので、コードで行う方法を採用しましたが…)。
参考
Kotlin : ビット演算メモ
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 } }
参考
- WebView | Android Developers
- android - How to enable zoom controls and pinch zoom in a WebView? - Stack Overflow
- android - WebView double tap zoom not working on a Motorola Droid A855 - Stack Overflow
- Android Fragment handle back button press - Stack Overflow
- java - android webview stay in app - Stack Overflow
(本当に 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>