Android

AndroidでPDFViewerを作成する 第7回

Posted 9月 2 2014 by 須藤 雄一  ,

本連載の趣旨

Androidのアプリを作るぞ!と一念発起したはいいものの…開発環境は作ってHelloWorldを出してみたけど次に何をすればいいかわからない。1冊本を読んでみたけど、実際の開発はどのように進めていくのか想像がつかない。などなど。
初級者から中級者へステップアップするために必要な「何かアプリを作ってみる」ということに焦点を当てた連載となっています。
脱初心者を目指す連載となっていますので、ごくごく基本的な説明は割愛しています。また、Javaのプログラムに関しても「一冊本を読んだことがある」程度の読者を想定していますので、基本的な話は割愛していきますのでご了承ください。
題材は、「本棚を模したPDFViewer」です。

今後の課題

  • 新しいPDFデータはサーバーからダウンロードできるようにする。(アプリ内に保存しない)
  • PDFデータはアプリ内課金で購入できるようにする。
  • 本棚には、PDF以外にWEBページ(ブックマークのようなもの)を登録できるようにする。
  • PDFデータは可能な限り複製不可となるように保存する。
  • ソート、検索機能

今回の目標(本棚に表示するサムネイルを動的に生成する)

これまではImageViewを直接レイアウトファイルのactivity_main.xml上に配置していましたが、この方法ではプログラムから動的に配置することが困難です。
今回のように画像を格子状に配置するために用いるウィジェットはGridViewを用いることで比較的簡単に実装することができます。(とはいっても結構プログラムは長くなります…)
API Guide (GridView)

GridViewに文字を表示する

まずは、activity_main.xmlファイルを修正します。既存のImageViewを削除し、代わりにGridViewを配置します。

<RelativeLayout 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"
    android:background="@drawable/bookshelf"
    tools:context="jp.ssie.pdfviewerblog.MainActivity" >

    <GridView
        android:id="@+id/gridView1"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_alignParentLeft="true"
        android:layout_alignParentTop="true"
        android:numColumns="3" >
    </GridView>

</RelativeLayout>

次に、MainActivity.javaを修正します。
GridViewの使い方は、ListViewの使い方と基本的には同じです。次の3ステップです。

  1. 元データとなる配列かListを作成する
  2. Adapterを作成する
  3. 作成したAdapterをGridViewに関連付けする

まずは画像ではなく文字列を表示してみましょう。

これまでImageViewを使っていた部分を全てGridViewに置き換え、さらにクリック処理を実装するにはOnClickListenerではなくListViewと同様にOnItemClickListenerとなるため削除しておきます。(OnItemClickListenerは次回以降に実装していきます。)

// public class MainActivity extends ActionBarActivity implements OnClickListener {
public class MainActivity extends ActionBarActivity{

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);

		DBHelper helper = new DBHelper(this);
		SQLiteDatabase db = helper.getWritableDatabase();
		
		String[] list = {"image1", "image2", "image3", "image4", "image5"};
		ArrayAdapter<String> adapter = new ArrayAdapter<String>(this, 
				android.R.layout.simple_list_item_1, list);
		
		GridView gridView = (GridView) findViewById(R.id.gridView1);
		gridView.setAdapter(adapter);
//		ImageView imageView1 = (ImageView) findViewById(R.id.imageView1);
//		imageView1.setOnClickListener(this);
//		ImageView imageView2 = (ImageView) findViewById(R.id.imageView2);
//		imageView2.setOnClickListener(this);
	}
	
//	@Override
//	public void onClick(View v) {
//		// onClickの引数を使って条件分岐
//		// 「imageView1」をクリックした場合
//		if(v.getId() == R.id.imageView1){
//			String path = "/mnt/sdcard/android.pdf"; // 表示するpdfのパス(ここだけ異なる)
//			Uri uri = Uri.parse(path);
//			Intent intent = new Intent(this, MuPDFActivity.class);
//			intent.setAction(Intent.ACTION_VIEW);
//			intent.setData(uri);
//			startActivity(intent);
//
//		// 「imageView2」をクリックした場合
//		}else if(v.getId() == R.id.imageView2){
//			String path = "/mnt/sdcard/html5.pdf"; // 表示するpdfのパス(ここだけ異なる)
//			Uri uri = Uri.parse(path);
//			Intent intent = new Intent(this, MuPDFActivity.class);
//			intent.setAction(Intent.ACTION_VIEW);
//			intent.setData(uri);
//			startActivity(intent);
//		}
//	}
}

削除するコードはコメントアウトしました。追加する部分は12行目から17行目だけです。

修正が完了したら実行してみましょう。
device-2014-09-02-220726

うまくいきました。文字が格子状に表示されているのがわかります。(本棚とのズレはとりあえず無視しておきましょう。)

GridViewに画像を表示させてみる

次は実際に画像を表示させてみます。
今回はAdapterに設定するレイアウトファイルを「android.R.layout.simple_list_item_1」としました。このレイアウトファイルはAndroid SDKの中にあらかじめ組み込まれているレイアウトのうちの1つで、単にTextViewが1つだけ定義されているだけにすぎません。画像を表示するには新たにレイアウトファイルを作成し、その中にImageViewを設定します。

ファイル名は「image_list_item.xml」としてみました。前回作成したImageViewは縦幅と横幅を65dpにすればちょうど良い設定となっていましたのでその設定をそのまま使いたいと思います。
・res/layout/image_list_item.xml

<ImageView xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/imageView1"
    android:layout_width="65dp"
    android:layout_height="65dp" />

PDFBookクラスというBeanクラスを作成します。このクラスはサムネイル(Bitmap)とPDFファイルを格納しているURIを保持します。BitmapはGridViewにサムネイルを表示するときに使用するために、URIはMuPDFに渡しPDFを表示するために保持しています。
Adapterの基となるListの中で、このBeanクラスから生成したインスタンスを保持するイメージです。

public class PDFBook{
	private Bitmap thum;
	private Uri uri;
	
	public Bitmap getThum() {
		return thum;
	}

	public void setThum(Bitmap thum) {
		this.thum = thum;
	}

	public void setUri(Uri uri) {
		this.uri = uri;
	}

	public Uri getUri() {
		return uri;
	}
}

次にArrayAdapterのBitmap版を作成します。

public class BitmapAdapter extends ArrayAdapter<PDFBook>{
	private int resourceId;

	// コンストラクタ
	public BitmapAdapter(Context context, int resource, List<PDFBook> objects) {
		super(context, resource, objects);
		resourceId = resource;
	}

	@Override
	public View getView(int position, View convertView, ViewGroup parent) {
		// Viewを使いまわさない場合
		if (convertView == null) {
			LayoutInflater inflator = (LayoutInflater) getContext()
					.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
			convertView = inflator.inflate(resourceId, null);
		}

		// R.layout.image_list_item上のImageViewをインスタンス化
		ImageView imageView = (ImageView) convertView.findViewById(R.id.imageView1);
		// ImageViewにサムネイル画像を設定する
		imageView.setImageBitmap(getItem(position).getThum());
		
		return convertView;
	}
}

ArrayAdapterをnewするときを思い出しましょう。第1引数にはContext、第2引数にはレイアウトファイルのリソースID、第3引数には配列もしくはListを渡したはずです。
同じようにBitmapAdapterにもこれら3つの引数を渡すことのできるコンストラクタを定義します。

また、getViewメソッドをオーバーライドします。
このメソッドはListViewやGridViewの子Viewが画面に表示されるときに呼び出されるメソッドで、ここに記述されているコードは定型的な記述となります。
スクロールが伴うようなListViewやGridViewは、リソースの節約のために一度作成した子View(画面から消えていった子View)を再利用しようとします。新たに生成するより再利用した方がコストが少なく済むためです。再利用が行われるときには、そのViewが第2引数のconvertViewに格納されてgetViewメソッドが実行され、新規作成の場合にはnullが入った状態でgetVeiwメソッドが実行されます。
そのため、convertViewがnullかどうかチェックし、nullの場合はLayoutInflaterクラスを使って新たにViewを生成します。

このconvertViewは上記で作成した「R.layout.image_list_item」のオブジェクトが格納されているイメージです。
ですので、convertView上のImageViewにサムネイル画像を設定することで、GridViewに画像を表示させることが可能となります。

MainActivityで行っているように、いつもどおりにImageViewをインスタンス化していますが、findViewByIdメソッドの前に「convertView」が付いているところに注目してください。convertView上から「R.id.imageView1」を探していることになります。
インスタンス化ができてしまえば、あとはsetImageBitmapメソッドを使ってBitmapを設定すればOKです。
このときgetItemメソッドを使用すると、コンストラクタの第3引数で渡したListの中から引数のposition番目のPDFBookオブジェクトを取り出すことができます。このPDFBookはサムネイル画像とするBitmapを保持していますので、ゲッターを使って取り出します。

では最後に、もう一度MainActivityです。
コメントアウトされている部分は削除するプログラムで、追加するのは15行目~17行目と、23行目~39行目です。

public class MainActivity extends ActionBarActivity{

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);

		DBHelper helper = new DBHelper(this);
		SQLiteDatabase db = helper.getWritableDatabase();
		
//		String[] list = {"image1", "image2", "image3", "image4", "image5"};
//		ArrayAdapter<String> adapter = new ArrayAdapter<String>(this, 
//				android.R.layout.simple_list_item_1, list);
		
		List<PDFBook> list = load();
		BitmapAdapter adapter = new BitmapAdapter(this,
				R.layout.image_list_item, list);
		
		GridView gridView = (GridView) findViewById(R.id.gridView1);
		gridView.setAdapter(adapter);
	}
	
	private List<PDFBook> load(){
		List<PDFBook> list = new ArrayList<PDFBook>();
		
		// Android
		PDFBook book1 = new PDFBook();
		book1.setThum(BitmapFactory.decodeFile("/mnt/sdcard/android.jpg"));
		book1.setUri(Uri.parse("file:///mnt/sdcard/android.pdf"));
		list.add(book1);
		
		// HTML5
		PDFBook book2 = new PDFBook();
		book2.setThum(BitmapFactory.decodeFile("/mnt/sdcard/html5.jpg"));
		book2.setUri(Uri.parse("file:///mnt/sdcard/html5.pdf"));
		list.add(book2);
		
		return list;
	}
}

元データとなる配列とAdapterを生成していた部分を新たに作成したPDFBookクラスとBitmapAdapterをつかって書き換えています。
Listを作る部分はloadメソッドとして外だしにしていますが、これは今後データベースと連携していくためコードが長くなることが予想できるためです。

では、ここまでで一旦実行してみましょう。
実行する前に「/mnt/sdcard/」ディレクトリ以下に「android.jpg」と「html5.jpg」をアップロードするのを忘れずに行ってください。
device-2014-09-02-225655

なんとか画像が表示されてくれました。
ですが、うーん…いまいちですね…
まだまだチューニングが必要そうです。

今回は中途半端ですがこの辺で一区切りにしたいと思います。
次回は引き続きGridViewを使ってサムネイルを動的に生成するプログラムを完成に近づけていこうと考えています。


Android Android Tips



  未経験OKの仕事 |  上場企業の仕事 |  高待遇の仕事 |  外資系の仕事 |  社内SEで検索 |  自社サービスで検索





メールアドレス
ご質問・問い合わせ等、ご自由にお書きください。