蔵書詳細の元ウェブページの表示

蔵書詳細の元となるウェブページを表示する中でWebViewを使ったWebページの実装を学習します。

操作性・ユーザ体験の改善からの引き続きの学習ページです。

学習ポイント

Androidアプリにて自身(または法人)で管理している既存のホームページなどを表示したいなどの場合があります。
その場合にAndroidではアプリ内で表示する方法、またはAndroidデバイスにインストールされたブラウザアプリ(Chromeやmobile safari)にURLを教えて表示する2つの方法があります。
本ページでは上記2つの機能を実装しながら機能を学習します。

WebView と Chrome Custom Tabs

最近はAndroidに置いてChrome Custom TabsというWebViewやChromeアプリより高速な表示を行えるコンポーネントも出てきました、これには一部デメリットもあるのですが、非常に高速でセキュリティ面を考慮したコンポーネントになっています。

メリット & デメリット

コンポーネント メリット デメリット
WebView ・URL毎のハンドリング可
・javascriptの実行可
・ソーシャルログイン不可
Chrome Custom Tabs ・表示が早い
・ソーシャルログイン可
・URL毎のハンドリング不可
・javascriptの実行不可

ソーシャルログインに関してはGoogle Developersでも勧告され2017/4/20よりWebViewでのソーシャルログインは廃止されGoogleのSDKかChrome Custom Tabsでの実装に切り替えるよう支持されています。

WebView表示の実装

新しくFragmentを作成しレイアウトにWebViewを配置し、幅や高さを設定していきます。
今回はProjectウィンドウからFragmentディレクトリを選択した状態で新規Fragment作成の手順を行います。

New -> Fragment -> Fragment(Blank)

項目 設定値
Fragment Name BTWebViewFragment
Create Layout XML? チェックをつける
Fragment Layout Name fragment_btweb_view
Include fragment factory methods? チェックをつけない
Include interface callbacks? チェックをつけない
Source Language Java

WebViewでの表示を行うために現状からは以下3つの修正が必要になります。

  1. Modelクラス(DetailDataModel.java)にWebページリンクを取得するパラメータ(変数)を追加
  2. WebView表示Fragmentの実装
  3. DetailFragmentクラス → WebView表示Fragmentへの遷移ボタンを配置

1.DetailDataModelクラスを編集

WebViewへのURLを調べるためにGoogle Androidアプリ開発ガイドの蔵書単体情報を取得するAPIの検索結果を確認します。
APIの結果にはpreviewLinkinfoLinkという項目があり、それぞれのURLをさらにブラウザで確認するとGoogle BooksのURLとPlayStoreの書籍画面のURLであることがわかります。

また2つの情報の階層としてはVolumeInfoの中であることがわかりましたので、一旦この項目2つをDetailDataModelクラスのパラメータに追加します。

DetailDataModel.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 蔵書概要クラス
public class VolumeInfo {
...一部省略
// 蔵書サムネイル画像URL
public ImageLinks imageLinks;
//↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓追加↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
// Google BooksへのリンクURL
public String previewLink;
// Play StoreへのリンクURL
public String infoLink;
//↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑追加↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑
}

// 蔵書サムネイルクラス
public class ImageLinks {
// 蔵書小サイズサムネイル
public String smallThumbnail;
// 蔵書サムネイル
public String thumbnail;
// 中サイズ表示画像
public String medium;
}

取得パラメータの追加は完了しました、取得したパラメータのURLはBTWebViewFragmentへ遷移する時の連携データとして使用します。

2.WebView表示Fragmentの実装

続いて実際に表示するBTWebViewFragmentを実装していきます、レイアウトにはWebViewコンポーネントを配置し、BTWebViewFragmentクラスにはWebViewでの表示プログラムを実装します。

レイアウト作成

以下ファイルを開き、テキストエディタモードでレイアウトを編集します。

app -> res -> layout -> fragment_btweb_view.xml

fragment_btweb_view.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="kuririnz.xyz.bookdiscovery.Fragment.BTWebViewFragment">

<!-- ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓削除↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ -->
<!-- TODO: Update blank fragment layout -->
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="@string/hello_blank_fragment" />
<!-- ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑削除↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ -->
<!-- ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓追加↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ -->
<WebView
android:id="@+id/FragmentWebView"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<!-- ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑追加↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ -->
</FrameLayout>

BTWebViewFragmentに置けるレイアウトとしてはWebViewで対象URLのウェブページを表示するだけなのでWebViewコンポーネントをは配置し画面全体に表示して完了です。
FrameLayoutは他のLayout(LinearLayout,RelativeLayout,ConstraintLayout)と違い子Viewを配置するための便利な機能を持ち合わせていないことが特徴で、
今回のように単純なレイアウトであったり、表示座標を固定して表示したい場合にのみFrameLayoutコンポーネントを使用することをお勧めします。

BTWebViewFragment実装

BTWebViewFragmentクラスは蔵書詳細画面から表示するURLデータを連携してもらい、
アプリ内のWebViewで表示するための設定をしてからURLの読み込みを開始するよう実装します。

BTWebViewFragment.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
public class BTWebViewFragment extends Fragment {

//↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓追加↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
// データ連携用定数
public static final String BUNDLE_URL = "BUNDLE_URL";

// バインドコンポーネント
private WebView webview;
// メンバ変数
private String defaultUrl;

// スタティックコンストラクタ
public static BTWebViewFragment getInstance(String previewLink) {
// BTWebViewFragmentインスタンスを生成
BTWebViewFragment fragment = new BTWebViewFragment();
// BTWebViewFragmentへデータを渡すためのBundleを初期化
Bundle args = new Bundle();
// Google Booksのウェブページリンクをデータ渡し
args.putString(BUNDLE_URL, previewLink);
// データ格納クラスをBTWebViewFragmentインスタンスにセット
fragment.setArguments(args);
// 生成したFragmentを返却
return fragment;
}
//↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑追加↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑

...一部省略

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_web_view, container, false);
}
//↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓追加↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);

// 遷移時の連携データを取得
if (getArguments() != null) {
// 遷移時に登録したKeyValueデータがない場合はGoogleページを表示
defaultUrl = getArguments().getString(BUNDLE_URL, "https://www.google.co.jp/");
}

// レイアウトのコンポーネントをバインド
webview = getView().findViewById(R.id.FragmentWebView);
// 自身のWebViewで表示するためにWebViewClientをWebViewに設定
webview.setWebViewClient(new WebViewClient());
// URLの読み込み
webview.loadUrl(defaultUrl);
}
//↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑追加↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑
}

これでBTWebViewFragmentクラスの実装も完了です。

3.DetailFragmentクラス → WebView表示Fragmentへの遷移ボタンを配置

最後にDetailFragmentからBTWebViewFragmentへ遷移するためのボタンを配置し、遷移するためのコードを実装します。
以下のようなレイアウトになるように実装します。

fragment_detail.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
...一部省略
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:weightSum="2">
<ImageView
android:id="@+id/DetailImage"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:paddingLeft="8dp"
android:scaleType="fitCenter"
android:adjustViewBounds="true"/>
<LinearLayout
android:orientation="vertical"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1">
<TextView
android:id="@+id/DetailSubTitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
tools:text="蔵書サブタイトル"/>
<TextView
android:id="@+id/DetailAuthor"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
tools:text="蔵書作者名"/>
<Button
android:id="@+id/TransitionWebView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="WebViewで確認"/>
</LinearLayout>
</LinearLayout>
...一部省略

DetailFragment.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
//↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓修正↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
public class DetailFragment extends Fragment implements View.OnClickListener {
//↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑修正↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑

// 定数
// データ渡しのキー情報
private final static String BUNDLE_KEY = "BUNDLE_SELFLINK";

// xmlファイルのコンポーネントと関連付ける要素
private TextView titleText;
private TextView subTitleText;
private TextView authorText;
private TextView descriptText;
private TextView pageText;
private TextView publishDateText;
private ImageView detailImage;
//↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓追加↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
private Button transWebviewBtn;
// Play Store リンクURL
private String infoLink;
//↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑追加↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑
// 個体リンクのURL
private String selfLink;

...一部省略

@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
...一部省略
detailImage = getView().findViewById(R.id.DetailImage);
//↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓追加↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
transWebviewBtn = getView().findViewById(R.id.TransitionWebView);

// BTWebViewFragmentへの遷移処理を実装
transWebviewBtn.setOnClickListener(this);
//↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑追加↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑

...一部省略
@Override
public void onResponse(Call call, Response response) throws IOException {
// JsonパースライブラリGsonのインスタンス化
Gson gson = new Gson();
// 返却されたJson文字列を一旦変数に代入
String jsonString = response.body().string();
// DetailDataModelクラスに代入
DetailDataModel detailData = gson.fromJson(jsonString, DetailDataModel.class);
//↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓追加↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
// Play Store へのリンクを代入
infoLink = detailData.volumeInfo.infoLink;
//↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑追加↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑
// パースが正常に行えたかLogcatに出力して確認。
Log.d("DetailFragment parse", detailData.volumeInfo.title);
// MainThreadに処理を渡し画面にデータを反映する
handler.post(new ReflectDetail(detailData));
}
...一部省略
}

//↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓追加↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
// ボタンクリック時のイベントを実装
@Override
public void onClick(View view) {
// クリックされたボタンをIDで判定
if (view.getId() == R.id.TransitionWebView) {
// "WebViewで確認"ボタンをクリックした場合
// BTWebViewFragmentをインスタンス化
BTWebViewFragment fragment = BTWebViewFragment.getInstance(infoLink);
// 別のFragmentに遷移するためのクラスをインスタンス化
FragmentTransaction ft = getFragmentManager().beginTransaction();
// 現在、DetailFragmentを表示しているR.id.FragmentContainerをBTWebViewFragmentに置き換え
ft.replace(R.id.FragmentContainer, fragment);
// 表示していたFragmentをバックスタックに追加
ft.addToBackStack(null);
// 変更を反映
ft.commit();
}
}
//↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑追加↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑
...一部省略
}

上記の通り、実装を進めると以下の箇所でエラーが出てくると思われます。

ft.replace(R.id.FragmentContainer, fragment);

原因はBTWebViewFragmentクラスで継承しているFragmentクラスの問題になります。
以前に蔵書詳細画面作成でFragmentクラスは以下の2種類あると解説をしました。

Android SDK上にはFragmentクラスが2種類あります。

  • android.app.Fragment
  • android.support.v4.app.Fragment

DetailFragmentが継承しているFragmentはandroid.support.v4.app.Fragmentになっているため、getFragmentManager()メソッドで取得できるFragmentManagerクラスはsupport.v4のFragmentクラスだけということになります。
AppCompatAcvitiyクラスではgetFragmentManager()getSupportFragmentManager()の2つメソッドがありFragmentとの関連は以下の通りになります。

ということになります、気になる場合はResultListActivityの実装を確認してみてください。
ではDetailFragmentのエラーを解消するためにBTWebViewFragmentを修正します。

BTWebViewFragment.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package kuririnz.xyz.bookdiscovery.Fragment;

//↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓削除↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
import android.app.Fragment;
//↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑削除↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑
//↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓追加↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
import android.support.v4.app.Fragment;
//↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑追加↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.webkit.WebView;
import android.webkit.WebViewClient;

import kuririnz.xyz.bookdiscovery.R;

/**
* 蔵書詳細情報のウェブページを表示するFragment
*/
public class BTWebViewFragment extends Fragment {
...一部省略
}

上記実装ができたら動作確認します。
蔵書詳細画面から追加したWEBVIEWで確認ボタンをクリックしてアプリ内webViewで表示されることを確認します。

BTWebViewFragmentクラスの実装でアプリ内のWebViewで指定URLのウェブページを表示することができました、WebView内でリンクをタップするなどして遷移したウェブページでソフトウェアバックボタンをクリックすると蔵書詳細画面に戻ってしまうと思いますが、ハンドリングするためにはBTWebViewFragmentonCreateViewメソッドでレイアウト生成したViewインスタンスのsetOnKeyLisnerメソッドを実装する必要があります。

また、WebViewをURL毎にハンドリングしたり、表示したウェブページに対してjavascriptのコードを実行する場合などは、
WebViewClientクラスを継承した自作クラスを作成し、各メソッドをオーバーライドして実装する必要があります。

BTWebViewFragment.java
1
2
// 自身のWebViewで表示するためにWebViewClientをWebViewに設定
webview.setWebViewClient(new WebViewClient());

WebViewでのハンドリング等は当ページの後半で解説します。
一旦は指定URLをWebViewで表示する実装に関しては以上になります。

既存のブラウザアプリでウェブページを表示する

BTWebViewFragmentへの遷移ボタンの下にもう一つボタンを追加し、ブラウザで表示するための機能を実装します。
他のアプリを動作させる時の要素としてはIntentを使用することを以前のページにも記載していますが、ブラウザでURLを開く場合もIntentを使用して実装します。
レイアウトは以下の様になる様に実装します。

レイアウトの修正

fragment_detail.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
...一部省略
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:weightSum="2">
<ImageView
android:id="@+id/DetailImage"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:paddingLeft="8dp"
android:scaleType="fitCenter"
android:adjustViewBounds="true"/>
<LinearLayout
android:orientation="vertical"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1">
<TextView
android:id="@+id/DetailSubTitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
tools:text="蔵書サブタイトル"/>
<TextView
android:id="@+id/DetailAuthor"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
tools:text="蔵書作者名"/>
<Button
android:id="@+id/TransitionWebView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="WebViewで確認"/>
<Button
android:id="@+id/TransitionBrouser"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="ブラウザアプリで確認"/>
</LinearLayout>
</LinearLayout>
...一部省略

DetailFragmentの実装

DetailFragment.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
public class DetailFragment extends Fragment implements View.OnClickListener {

...一部省略
private Button transWebviewBtn;
//↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓追加↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
private Button transitionBrowserBtn;
//↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑追加↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑
// Play Store リンクURL
private String infoLink;
// 個体リンクのURL
private String selfLink;

...一部省略

@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
...一部省略
detailImage = getView().findViewById(R.id.DetailImage);
transWebviewBtn = getView().findViewById(R.id.TransitionWebView);
//↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓追加↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
transitionBrowserBtn = getView().findViewById(R.id.TransitionBrowser);

// クリック時にブラウザアプリでURLを表示する処理を実装
transitionBrowserBtn.setOnClickListener(this);
//↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑追加↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑
// BTWebViewFragmentへの遷移処理を実装
transWebviewBtn.setOnClickListener(this);

...一部省略
}

// ボタンクリック時のイベントを実装
@Override
public void onClick(View view) {
// クリックされたボタンをIDで判定
if (view.getId() == R.id.TransitionWebView) {
// "WebViewで確認"ボタンをクリックした場合
// BTWebViewFragmentをインスタンス化
BTWebViewFragment fragment = BTWebViewFragment.getInstance(infoLink);
// 別のFragmentに遷移するためのクラスをインスタンス化
FragmentTransaction ft = getFragmentManager().beginTransaction();
// 現在、DetailFragmentを表示しているR.id.FragmentContainerをBTWebViewFragmentに置き換え
ft.replace(R.id.FragmentContainer, fragment);
// 表示していたFragmentをバックスタックに追加
ft.addToBackStack(null);
// 変更を反映
ft.commit();
//↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓追加↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
} else if (view.getId() == R.id.TransitionBrowser) {
// "ブラウザアプリで確認"ボタンをクリックした場合
// ブラウザアプリで表示するURLをUriクラスにキャスト
Uri uri = Uri.parse(infoLink);
// ブラウザアプリで開くためのIntentをインスタンス化
Intent intent = new Intent(Intent.ACTION_VIEW, uri);
// ブラウザアプリで指定URLを表示する
startActivity(intent);
//↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑追加↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑
}
}
...一部省略
}

上記コードを実装したら動作確認します。
蔵書詳細画面のブラウザアプリで確認ボタンをクリックした時に正常にブラウザアプリが表示またはブラウザアプリの選択ダイアログが表示され、選択したブラウザアプリが立ち上がることを確認します。

Android OSはIntentを受け取ると第二引数のスキーム設定によって表示するアプリを判断します。
Intentのコンストラクタの第一引数ではIntentが実行する処理を指定します、今回の第一引数はIntent.ACTION_VIEWを設定しており、この属性は第二引数に設定されるUriデータを処理できるアプリを表示する役割をIntentに与えることができます。

そして、第二引数で渡したUriデータはhttps://..のURL情報になっておりスキーム箇所は:の前までとして判断してください。
今回のスキームはhttps:となっており、処理できるアプリとしてブラウザアプリ(Chromeなど)を起動するようAndroid OSが判断します、対応するアプリが複数ある場合は表示するアプリを選択するダイアログの様な画面が表示され、ダイアログから選択したアプリが起動します。

電話アプリやメーラーアプリ、自社開発の他アプリを起動することが多く、ほとんどの場合Intentの第一引数はIntent.ACTION_VIEWを設定して使用します。

以上でブラウザアプリでの確認機能の実装解説は終了です。

Chrome Custom Tabs

ブラウザアプリの様なレイアウトで表示され表示までの処理が最も早いコンポーネントとなるChrome Custom Tabsでの確認機能を実装します。

Chrome Custom TabsはSupport Libraryに含まれるコンポーネントのため、build.gradleファイルを修正する必要があります。
記述する”customtabs”のバージョン指定はimplementation 'com.android.support:appcompat-v7:XX.XX.XX'と同じバージョンを指定します。
以下は資料作成時のアプリのサンプルです。

build.gradle(Module: app)
1
2
3
4
5
6
7
8
9
10
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'com.android.support:appcompat-v7:27.0.2'
...一部省略
implementation 'com.squareup.okhttp3:okhttp:3.9.1'
compile 'com.google.code.gson:gson:2.2.4'
//↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓追加↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
compile 'com.android.support:customtabs:27.0.2'
//↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑追加↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑
}

続いてレイアウトは以下の様になる様に実装します。

レイアウトの修正

fragment_detail.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
...一部省略
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:weightSum="2">
<ImageView
android:id="@+id/DetailImage"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:paddingLeft="8dp"
android:scaleType="fitCenter"
android:adjustViewBounds="true"/>
<LinearLayout
android:orientation="vertical"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1">
...一部省略
<Button
android:id="@+id/TransitionWebView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="WebViewで確認"/>
<Button
android:id="@+id/TransitionBrouser"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="ブラウザアプリで確認"/>
<Button
android:id="@+id/TransitionCustomTabs"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="ChromeCustomTabsで確認"/>
</LinearLayout>
</LinearLayout>
...一部省略

DetailFragmentの実装

DetailFragment.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
public class DetailFragment extends Fragment implements View.OnClickListener {

...一部省略
private Button transWebviewBtn;
private Button transitionBrowserBtn;
//↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓追加↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
private Button transCustomTabsBtn;
//↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑追加↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑
// Play Store リンクURL
private String infoLink;
// 個体リンクのURL
private String selfLink;

...一部省略

@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
...一部省略
detailImage = getView().findViewById(R.id.DetailImage);
transWebviewBtn = getView().findViewById(R.id.TransitionWebView);
transitionBrowserBtn = getView().findViewById(R.id.TransitionBrowser);
//↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓追加↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
transCustomTabsBtn = getView().findViewById(R.id.TransitionBrowser);

// クリック時にChrome Custom TabsでURLを表示する処理を実装
transCustomTabsBtn.setOnClickListener(this);
//↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑追加↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑
// クリック時にブラウザアプリでURLを表示する処理を実装
transitionBrowserBtn.setOnClickListener(this);
// BTWebViewFragmentへの遷移処理を実装
transWebviewBtn.setOnClickListener(this);

...一部省略
}

// ボタンクリック時のイベントを実装
@Override
public void onClick(View view) {
// クリックされたボタンをIDで判定
if (view.getId() == R.id.TransitionWebView) {
// "WebViewで確認"ボタンをクリックした場合
// BTWebViewFragmentをインスタンス化
BTWebViewFragment fragment = BTWebViewFragment.getInstance(infoLink);
// 別のFragmentに遷移するためのクラスをインスタンス化
FragmentTransaction ft = getFragmentManager().beginTransaction();
// 現在、DetailFragmentを表示しているR.id.FragmentContainerをBTWebViewFragmentに置き換え
ft.replace(R.id.FragmentContainer, fragment);
// 表示していたFragmentをバックスタックに追加
ft.addToBackStack(null);
// 変更を反映
ft.commit();
} else if (view.getId() == R.id.TransitionBrowser) {
// "ブラウザアプリで確認"ボタンをクリックした場合
// ブラウザアプリで表示するURLをUriクラスにキャスト
Uri uri = Uri.parse(infoLink);
// ブラウザアプリで開くためのIntentをインスタンス化
Intent intent = new Intent(Intent.ACTION_VIEW, uri);
// ブラウザアプリで指定URLを表示する
startActivity(intent);
//↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓追加↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
} else if (view.getId() == R.id.TransitionCustomTabs) {
// "ChromeCustomTabsで確認"ボタンをクリックした場合
// ブラウザアプリで表示するURLをUriクラスにキャスト
Uri uri = Uri.parse(infoLink);
// Chrome Custom Tabsをインスタンス化
CustomTabsIntent tabsIntent = new CustomTabsIntent.Builder()
.setShowTitle(true)
.build();
// Custom Tabsを表示
tabsIntent.launchUrl(getActivity(), uri);
//↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑追加↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑
}
}
...一部省略
}

以上のコードを実装したら動作確認します。
蔵書詳細画面のChromeCustomTabsで確認ボタンをクリックした時に正常にChrome Custom Tabsが表示、またはブラウザアプリの選択ダイアログが表示され、選択した後にChrome Custom Tabsが表示されることが確認できると思います。

Chrome Custom Tabsは特殊なコンポーネントになっており、”Intent”に関してもCustomTabsIntentクラスを使います、このクラスの初期化は”Builderパターン”という形式で初期化する形式になっています。
今回はウェブページのタイトルを表示するオプション設定を行いましたが、他にも起動/終了時のアニメーションなど設定可能項目がいくつか存在します。
さらにIntentを起動する場合は初期化したCustomTabsIntentからlaunchUrlメソッドを使いChrome Custom Tabsで対象URLのウェブページを表示します。

以上でChrome Custom Tabsの実装解説は終了です。

WebViewでのURLハンドリング

AndroidアプリでWebViewを使う場合、多くの場合は個人(または法人)の管理サイト、またはサービス対象のウェブページに限った範囲でウェブページリンク遷移する場合は開発者が管理できます。
しかし、外部のウェブページなどにもアクセスできてしまう場合、開発者が管理できない場面(ブラウザアプリはログインできるが、WebViewではできないなど)が考えられるため、管理外のURLの表示などはブラウザアプリを起動したり、特定のURLを表示しようとした場合に管理内のURLを表示させるなどのハンドリング(ある状況における処理)を行うことが必要になります。

そのためにはWebViewClientクラスを継承した自作クラスを作成し、各メソッドをカスタマイズして実装する必要があります。
WebViewが次に読み込むURLを判別した後はWebViewClientのActivityやFragmentのライフサイクルと似た形でメソッドが実行されます。
実行されるメソッドは多いのでよく使うメソッドをいくつか紹介します。

メソッド名 実行されるタイミング
shouldInterceptRequest(WebView, String) ・API Level 23-
・画像等リソース読み込み時
shouldInterceptRequest(WebView, WebResourceRequest) ・API Level 24+
・画像等リソース読み込み時
shouldOverrideUrlLoading(WebView, String) ・API Level 23-
・次回読み込みURL確定時(読み込み前)
shouldOverrideUrlLoading(WebView, WebResourceRequest) ・API Level 24+
・次回読み込みURL確定時(読み込み前)
onPageStarted(WebView, String, Bitmap) HTML表示開始時
onLoadResource(WebView, String) HTML表示中の画像等表示時
onPageFinished(WebView, String) HTML表示終了時

多くの場合にはshouldOverrideUrlLoading()メソッドをオーバーライドしてカスタマイズすることで期待通りの動作に仕上げられると思います。
各メソッドの引数にはURL情報が含まれているのでURLを元に条件分岐 if構文などを使いハンドリングすることができます。

shouldOverrideUrlLoading()shouldInterceptRequest()メソッドは古いバージョンもサポート対象都する場合は2つのメソッドまたは第二引数がString型のメソッドを実装する必要があります。
ただ、第二引数がString型のメソッドはdeprecated(廃止予定)となっているため、サポート対象とするOSバージョンをAPI level 24以上にしてしまうことも検討すると良いと思います。

今回の蔵書検索アプリの資料の中では実際に実装箇所は含んでいませんが、表示するウェブページで実行できるプログラムjavascriptをアプリから実行することで、ウェブページ上のデータを取得したり、表示内容を変更することもできます。
実行の手順としては以下の通りです。

  1. WebViewインスタンスでjavascriptの実行を有効化
    最初のwebView.loadUrl()メソッドを実行する前に設定します。

    1
    webview.getSettings().setJavaScriptEnabled(true);
  2. shouldOverrideUrlLoading()メソッドで”javascript”のコードを実行

    1
    2
    3
    4
    5
    6
    7
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
    // Android OS 4.4(kitkat)~
    webView.evaluateJavascript("javascript:document.write('JavaScriptが実行!');", new ValueCallback<string>() { ...});
    } else {
    // Andorid OS ~4.3(jelly bean)
    webView.loadUrl("javascript:document.write('JavaScriptが実行!');");
    }

上記の通り、AndroidのOSバージョンによってjavascriptの実行方法に差異があるので注意して実装してください。
またAndroid OS 4.4(Kitkat)以降で使えるメソッドでは処理結果を受け取ることも簡単にできる様になっています。

以上で蔵書詳細の元ウェブページの表示の解説は終了です。