Qiitaビューア制作中にWebViewで調べたこと困ったこといろいろ

# はじめに

先日Android向け[QiitaビューアアプリQitches](http://play.google.com/store/apps/details?id=com.airdsoft.qitches)をリリースしました。
よく使う機能に素早くアクセスできる使いやすいアプリを目指して制作しました。
機能追加、UI改善など今後も行なって行きますのでフィードバックいただけると嬉しいです。

それはさておき、開発の中でWebview、Spinner、アニメーションあたりで困る事が結構ありました。
この記事ではその中のWebviewで調べた点、困らされた点について書いていこうと思います。  

## APIで取得した生のHTMLにスタイルを適用する
Qiita APIで記事の情報を取得すると、生のHTML文がString形式で取得されます。
そのままWebViewに設定すると真っ白な整形されていない文書が表示されます。
そのため、まずベースとなるCSSを適用したHTMLファイルをassets下に置いて読み込み、
そこにQiita APIで取得した記事タイトルや本文のStringを流し込んであげる必要があります。
今回はJsoupというライブラリを使用しました。CSSは頑張って作ってください。。

```article_base.html
<!DOCTYPE html>
<html>
<head lang="ja">
  <meta charset="UTF-8">
  <meta name='viewport' content="width=device-width, initial-scale=1.0, user-scalable=no">
  <title>Qitches</title>
  <link rel="stylesheet" type="text/css" href="html/css/contentstyle.css" />

</head>
<body>
  <div id="title"></div>
  <div id="content"></div>
</body>
</html>
```

```build.gradle
compile 'org.jsoup:jsoup:1.10.2'
```

```ContentsViewFragment.java
private void initWebView() {
    // 表示用HTML文を生成
    Document doc = generateHtmlToShow();
    mContentWebView.setWebChromeClient(new WebChromeClient());
    mContentWebView.setVerticalScrollBarEnabled(false);
    mContentWebView.setHorizontalFadingEdgeEnabled(false);
    mContentWebView.getSettings().setUseWideViewPort(true);
    mContentWebView.getSettings().setLoadWithOverviewMode(true);
    mContentWebView.setScrollBarStyle(View.SCROLLBARS_OUTSIDE_INSET);
    // WebViewにHTMLをセット
    mContentWebView.loadDataWithBaseURL("file:///android_asset/", doc.outerHtml(), "text/html", "utf-8 ", null);
}

@NonNull
private Document generateHtmlToShow() {
    String contents = null;
    try {
        // assets下のHTMLファイルを読み込む
        contents = AssetUtils.readFromAssets(getContext(), "html/article_base.html");
    } catch(IOException e) {
        Log.e("TAG", "IOException occurred");
    }
    Document doc = Jsoup.parse(contents);
    Element title = doc.getElementById("title");
    title.append(mTitle); // タイトルを"title"の箇所に流し込む
    Element elem = doc.getElementById("content");
    elem.append(mContents); // 本文を"content"の箇所に流し込む
    return doc;
}
```

## 画面回転時にスクロール位置が保持されない
Qiitaの記事はほとんどの場合ソースコードが含まれていますので、
コードを閲覧しやすくするために途中でスマホを横向きにすることが結構あるかと思います。
そうすると、記事の途中まで読み進めていたにも関わらず、スクロール位置が先頭に戻ってしまいます。

通常のViewの場合、idが振られていれば画面回転時にも状態が保持されるため深く考えなくて良いのですが、
WebViewの場合は`onSaveInstanceState`で`saveState`を呼ぶというやり方で状態保存する必要があります。
今回はFragment内にWebViewを置いた場合のコードを示しますがActivityに直置きでも恐らく変わらないと思います。

```ContentsViewFragment.java
@Override
public void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    mContentWebView.saveState(outState); // ★ここで状態保存
}

```

保存した状態を復元するには以下のように`restoreState`を呼ぶ必要があります。
状態復元しなかった場合だけHTML読み込みなどの初期化処理を行ってあげればOKです。

```ContentsViewFragment.java
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
                            Bundle savedInstanceState) {
    View view = inflater.inflate(R.layout.fragment_contents_view, container, false);
    LinearLayout parentLayout = (LinearLayout)view.findViewById(R.id.container_layout);
    mContentWebView = new WebView(getActivity());
    mContentWebView.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.MATCH_PARENT));

    if (savedInstanceState != null) {
        mContentWebView.restoreState(savedInstanceState); // ★状態保存されている場合復元
    } else {
        initWebView(); // ★状態復元しなかった場合(初回表示の場合)は初期化処理
    }
    parentLayout.addView(mContentWebView);
    return view;
}
```

## Activity遷移時のTransitionが適用されない 
Activity遷移時にExplode/Slide/FadeなどのActivity Transitionを適用する際に
Activity内にWebViewがあるとWebViewにだけアニメーションが適用されません。
(Activity Transitionとは[ここ](https://guides.codepath.com/android/Animations#activity-transitions-in-lollipop)のActivity Transitions in Lollipopの項にあるやり方を指します。)
結果、Toolbarだけがスライドインしてきたり、画面がチラつくだけだったりとても格好悪いことになります。
WebViewに対して以下のように`setTransitionGroup(true)`することで適用されるようになります。

```ContentsViewFragment.java
mContentWebView.setTransitionGroup(true); // これを指定しないとアニメーションが適用されない
```

ちなみに公開したQitchesアプリでは、結局Transitionを使わずに`overridePendingTransition`で
アニメーションすることにしたためこの指定を行わなくても正しくアニメーションします。

参考：
[CODEPATH Animation](https://guides.codepath.com/android/Animations)
[Stackoverflow](https://stackoverflow.com/questions/27317044/webview-glitches-in-shared-element-transition-on-lollipop)

## スマホの画面幅からコンテンツがはみ出る
これはWebViewというよりCSSの書き方をよく知らなかったのが問題なのですが一応。
スマホの画面幅で文章が勝手に折り返してほしいのに折り返されずにはみ出ることがありました。
左の画像がはみ出ている物です。はみ出ている部分以外は真っ白なのに横スクロールできて格好悪いです。
はみ出る場合は折り返すようにCSSで設定してあげると右の画像のようにすることができます。
ちなみに公開したQitchesアプリでは、Tableだけは敢えて折り返さないようにしています。
無理やり折り返しても逆に見づらくなってしまうためです。


```.css
body #content {
word-wrap: break-word;
overflow-wrap : break-word;
}
```

![折り返し.png](https://qiita-image-store.s3.amazonaws.com/0/99570/699a33da-c51e-11f5-639e-eca27211ec35.png)

## デフォルトのマージンが上下左右に入る
WebViewにはデフォルトのマージンが設定されているようです。
記事のタイトルを先頭に挿入した場合、左の画像のようにマージンが入ってしまい格好悪いことになります。
以下のようにcssファイルの先頭でmarginとpaddingを0クリアすることで右の画像のようにできます。

```.css
* {margin:0;padding:0;}
```

![margin.png](https://qiita-image-store.s3.amazonaws.com/0/99570/0dbe40c3-dfec-e17c-fe1d-674d61a47208.png)


# おわりに
WebViewはなかなか癖があるな～と思いました。Web系の人には扱いやすいのかもですが。
今後もWebViewで何かあればこの記事を更新していきます。