SQLの窓

2016年10月21日


シンプル Android Data Binding : Android Studio 2.2 / 古い定義との違いと、以前のプロジェクトでエラーが出る場合の対処

▼ 記事内のソースコードへ移動
@BindingAdapter でカスタムセッター
Syain クラスへ直接セットして表示
Google Gson と tools.jar を使用した REST API 経由の読み込み
Firebase API を使用した読込み


プロジェクトの Gradle に classpath "com.android.databinding:dataBinder:1.0-rc1" は必要ありません
Module(app) の Gradle に
    dataBinding {
        enabled = true
    }
があれば動作します
去年より地味に進化していました。build.gradle(Module: app) の簡単な設定で利用可能になります build.gradle(Module: app)
apply plugin: 'com.android.application'

android {
    compileSdkVersion 22
    buildToolsVersion "23.0.3"

    dataBinding {
        enabled = true
    }

    defaultConfig {
        applicationId "lightbox.june.bindingbasic"
        minSdkVersion 19
        targetSdkVersion 22
        versionCode 1
        versionName "1.0"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    testCompile 'junit:junit:4.12'
    compile 'com.android.support:appcompat-v7:22.2.1'
}

バインド用のクラスには、後々 Google Gson 使うので 超単純にしています。

※ @BindingAdapter はテキストを直接使えないコントロール用のカスタムセッターです

Syain.java

@{user.birthday} が app:dateText にセットされるよう画面で定義されているので、birthday が setDateText の引数として渡されます。
public class Syain {

	public String code;
	public String name;
	public String birthday;

	@BindingAdapter("dateText")
	public static void setDateText(DatePicker datePicker, String text) {

		if ( text != null && !text.equals("")) {
			Log.i("lightbox",text);

			int year = Integer.parseInt(text.substring(0, 4));
			int month = Integer.parseInt(text.substring(5, 7)) - 1;
			int day = Integer.parseInt(text.substring(8, 10));
			datePicker.updateDate(year, month, day);
		}

	}
}

画面も、layout 要素の記述がスリムになりました。
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto">
    <data>
        <variable name="user" type="lightbox.june.bindingbasic.Syain"/>
    </data>

        <LinearLayout
            android:orientation="vertical"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:padding="16dp">

            <TextView
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:id="@+id/textView"
                android:textSize="40dp"
                android:text="@{user.code}"/>

            <TextView
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:id="@+id/textView2"
                android:layout_marginTop="20dp"
                android:textSize="40dp"
                android:text="@{user.name}"/>

            <DatePicker
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:id="@+id/datePicker"
                android:layout_below="@+id/textView2"
                android:datePickerMode="spinner"
                android:calendarViewShown="false"
                app:dateText="@{user.birthday}"/>

        </LinearLayout>

</layout>

さて、MainActivity ですが、Android の公式ページがいまだに誤記しているのがタマに傷です。R.layout.main_activity では無く、R.layout.activity_main です。当たり前ですけれど。コピペするとえらい目にあいます。

MainActivity.java

最も単純なバインドのサンプルです。Syain クラスはコンストラクタも無く、JSON のプロパティは、public の同名の変数にセットされます。コメント部分は、JSON 文字列から Syain のインスタンスを作成して使用するパターンです( Google Gson は コンストラクタを使用しません )
package lightbox.june.bindingbasic;

import android.databinding.DataBindingUtil;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;

import lightbox.june.bindingbasic.databinding.ActivityMainBinding;

public class MainActivity extends AppCompatActivity {

	private ActivityMainBinding binding;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);

		binding = DataBindingUtil.setContentView(MainActivity.this,R.layout.activity_main);

//		String json = "{\"code\": \"0001\",\"name\": \"やまだ たろう\",\"birthday\": \"1959/10/10\"}";
//		Gson gson = new Gson();
//		Syain user = gson.fromJson(json,Syain.class);

		Syain user = new Syain();
		user.code = "0001";
		user.name = "やまだ たろう";
		user.birthday = "1959/10/10";

		binding.setUser(user);

	}
}

これで動くはずですが、良く解らないエラーが出る場合は以下の二つを試すといいはずです。画面に関しては以前からある問題点で、さらに動くまでは画面定義に日本語は直接書かないほうがいいと思います。

エラー時の対処 : パターン1
1) 画面定義の data 要素部分を削除してすぐ戻す
2) エラーが出るソースを開くと DataBindingUtil 部分のエラーがなくなる
3) binding.メソッドのエラーが残るので、プロジェクトをいったん閉じて開く
エラー時の対処 : パターン2
1) 画面定義の activity_main.xml を activity_main2.xml に変更して すぐに activity_main に戻す
2) ファイルメニューの Invalidate Caches / Restart を実行
Data Binding Android - Type parameter T has incompatible upper bounds : ViewDataBinding and MainActivity DataBindingUtil.setContentView - Type parameter T has incompatible upper bounds Google Gson と tools.jar を使用した読み込み ※ URL は、Firebase のログインの必要無いデータを使用しています
public class MainActivity extends AppCompatActivity {

	private ActivityMainBinding binding;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);

		// Data Binding 仕様の画面表示
		binding = DataBindingUtil.setContentView(MainActivity.this,R.layout.activity_main);

		// アクセスする URL 
		String url = "https://freebase-44e8b.firebaseio.com/class/0001.json?print=pretty";
		// URL から読み込み
		Tools.callHttpGet(url, "utf-8", new Tools.OnAsyncTaskListener() {
			@Override
			public void onAsyncTaskListener(String s) {
				Log.i("lightbox", s);
				Gson gson = new Gson();
				// JSON 文字列を Syain クラスのインスタンスに変換
				Syain user = gson.fromJson(s,Syain.class);
				// Data Binding で画面にデータをセット
				binding.setUser(user);
			}
		});

	}
}
Firebase Database REST API のドキュメント
Google Gson のダウンロード
tools.jar のダウンロード


Firebase API を使用した読込み
public class MainActivity extends AppCompatActivity {

	private ActivityMainBinding binding;
	private FirebaseDatabase database;
	private DatabaseReference mDatabase;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);

		// Data Binding 仕様の画面表示
		binding = DataBindingUtil.setContentView(MainActivity.this,R.layout.activity_main);

		// Firebase database 参照用
		database = FirebaseDatabase.getInstance();
		mDatabase = database.getReference();

		// データの参照
		mDatabase.child("class/0001").addListenerForSingleValueEvent(new ValueEventListener() {
			@Override
			public void onDataChange(DataSnapshot dataSnapshot) {
				// 取得したデータを Syain クラスのインスタンスとして取得
				Syain user = dataSnapshot.getValue(Syain.class);
				// Data Binding で画面にデータをセット
				binding.setUser(user);

			}

			@Override
			public void onCancelled(DatabaseError databaseError) {
				// Firebase database の参照に失敗した時の処理
				// ※ 参照権限がない場合等
			}
		});

	}
}

Firebase API + Android Studio : Database のデータを Java に取得する方法は3通りあります。
Firebase API + Android Studio : Database 処理の基本設定




posted by lightbox at 2016-10-21 04:00 | Comment(0) | Android Studio 2 | このブログの読者になる | 更新情報をチェックする

2016年10月10日


Android Studio で、ListView アプリケーションを作成するテンプレート

全てのソースコード



仕様の概略
1) MainActivity には ListView コントロールのみ
2) ListView 内に TextView は縦に2項目
3) NextActvity には、元へ戻るボタンと TextView が一つ
※ MyArrayAdapter を private で 一つのソースに記述しています。インターネットから JSON データを取得して ListView に表示します (https://lightbox.sakura.ne.jp/homepage/demo/data/csvtype/json.php) ➡ インターネットへのアクセスは、tools.jarTools.callHttpGet を使用します。 ➡ 読み出した JSON 文字列を Google Gson でデシリアライズして、JsonDataList にセットします。( リンク先には クラス定義があります ) このオブジェクトを ArrayAdapter<JsonData> を継承した MyArrayAdapter にセットし、ListView に MyArrayAdapter をセットして ListView を表示します。 ListView には、setOnItemClickListener で行をクリックしたイベントを定義し、その中で 行データを Intent にセットして次画面へ移動します。 次画面では、受け取った行データより、本文を取得して画面の TextView へ表示します。 MainActivity
public class MainActivity extends AppCompatActivity {

	private final int MY_REQUEST_CODE = 1000;
	private ListView listview;

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

		// リストビューの取得
		listview = (ListView) MainActivity.this.findViewById(R.id.listView);
		// リストビューの行をタップした時の処理
		listview.setOnItemClickListener(new AdapterView.OnItemClickListener() {
			@Override
			public void onItemClick(AdapterView<?> parent, View view, int position, long id) {

				// アダプターを取得
				MyArrayAdapter adapter = (MyArrayAdapter)parent.getAdapter();
				// 行データを取得
				JsonData json = (JsonData)adapter.getItem(position);

				// 引渡しデータを作成
				Intent intent = new Intent(MainActivity.this,NextActivity.class);
				intent.putExtra("DATA","こんにちは");
				// Serializable インターフェイスなのでまとめて渡す
				intent.putExtra("OBJECT",json);

				// 画面移動
				MainActivity.this.startActivityForResult(intent,MY_REQUEST_CODE);
			}
		});

		// JSON データの URL
		String json_url = "https://lightbox.sakura.ne.jp/homepage/demo/data/csvtype/json.php";
		// tools.jar の static メソッド
		Tools.callHttpGet(json_url, "utf-8", new Tools.OnAsyncTaskListener() {
			@Override
			public void onAsyncTaskListener(String s) {

				// JSON データをデシリアライズ
				Gson gson = new Gson();
				JsonDataList jdl = gson.fromJson(s, JsonDataList.class);

				// アダプタを作成して、データをセット
				MyArrayAdapter adapter
					= new MyArrayAdapter(MainActivity.this,R.layout.list_item);
				// jdl.item は配列
				adapter.addAll(jdl.item);

				// リストビューにデータを表示
				listview.setAdapter(adapter);

			}
		});
	}


	// ******************************
	// 次画面から戻って来た時の処理
	// ******************************
	@Override
	protected void onActivityResult(int requestCode, int resultCode, Intent data) {
		super.onActivityResult(requestCode, resultCode, data);

		if ( requestCode == MY_REQUEST_CODE ) {
			// NextActivity の 戻るボタン以外で戻ると 0
			Log.i("lightbox",String.format("NextActivity からの戻り値 : %d", resultCode));
			if ( data != null ) {
				String message = data.getStringExtra("DATA");
				Log.i("lightbox",String.format("NextActivity からのメッセージ : %s", message));
			}
		}

	}

	// ******************************
	// JsonData を格納する、この処理専用の
	// ArrayAdapter のカスタマイズ
	// ******************************
	private class MyArrayAdapter extends ArrayAdapter<JsonData> {

		// コンストラクタで渡された画面の保存
		private int mResource;

		public MyArrayAdapter(Context context, int resource) {
			super(context, resource);
			// ArrayAdapter でも、このようにして保存して利用してます
			mResource = resource;
		}

		@Override
		public View getView(int position, View convertView, ViewGroup parent) {

			if (convertView == null) {
				// 現在の View の取得
				LayoutInflater inflater = (LayoutInflater) MainActivity.this.getSystemService
					(Context.LAYOUT_INFLATER_SERVICE);
				convertView = inflater.inflate(mResource, null);
			}

			// アダプターより行データを取得
			JsonData json = MyArrayAdapter.this.getItem(position);

			// 画面にデータをセット
			TextView tv;

			// Subject
			tv	= (TextView) convertView.findViewById(R.id.textItem1);
			tv.setText(json.getSubject());

			// Name
			tv = (TextView) convertView.findViewById(R.id.textItem2);
			tv.setText(json.getName());

			// 行の画面をシステムに返す
			return convertView;
		}

	}

}

MainActivity での特記事項onItemClick 内での 行データの参照は、parent.getAdapter() によって、使用しているアダプターを取得します。
JsonData は、そのまま Intent にセットする為に Serializable インターフェイスを実装しています
JsonData へのデータのセットは、Google Gson 経由を想定しているので、setter および コンストラクタは定義していません
JsonData の getText 以外のメソッドはここでは使用しませんが、仕様の拡張を想定した最低限必要と判断したものです。特に、toString は、システムが用意した一つの項目しかない行を定義した画面で使う為に必要です(ここでは必要ありません)。
MyArrayAdapter で画面 id を内部保存しているのは、ArrayAdapter のソースコードに準じたものです


tools.jar は、ソースコードを簡略化する為のテスト用のライブラリです。本番には相応のコードを用意します。

NextActivity


public class NextActivity extends AppCompatActivity {

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

		// 引き渡された Intent を取得
		Intent intent = NextActivity.this.getIntent();
		// 文字列のデータを取り出す
		String message = intent.getStringExtra("DATA");
		Log.i("lightbox",String.format("MainActivity からのメッセージ : %s", message));
		// Serializable オブジェクトを取り出す
		JsonData json = (JsonData) intent.getSerializableExtra("OBJECT");

		// 画面へデータをセット
		TextView tv = (TextView) NextActivity.this.findViewById(R.id.textView);
		// text データ上の 改行文字列を実際の改行に変換する
		String text = json.getText();
		text = text.replace("\\n", "\n");
		tv.setText(text);

		// MainActivity へ戻る為のボタン
		Button button = (Button) NextActivity.this.findViewById(R.id.backButton);
		button.setOnClickListener(new View.OnClickListener() {
			@Override
			public void onClick(View view) {

				// 戻る時にも Intent を引き渡す
				Intent intent = new Intent();
				intent.putExtra("DATA", "今戻りました");
				// MainActivity で受け取る int データをセットする
				NextActivity.this.setResult(NextActivity.RESULT_OK,intent);
				// この画面を終了して MainActivity へ戻る
				NextActivity.this.finish();

			}
		});

	}
}

NextActivity での特記事項

Android Studio では、New => Activity で作成しており、AndroidManifest への定義は自動的に追加されます。


全てのソースコード


posted by lightbox at 2016-10-10 14:48 | Comment(0) | Android Studio 2 | このブログの読者になる | 更新情報をチェックする

2016年09月27日


Android : Data Binding で ListView へのデータ表示を凄く簡単にする

2016/09/27 : Data Binding の最新の仕様に合わせて内容を調整しました
( Gradle の変更は Module(app)用の gradle のみです )
2015/10/5 時点で、バグと思われる注意事項があります。XML のレイアウト定義内に日本語があると、バインドする為の解析処理が失敗するようです。文字列リソースに日本語を定義して参照するのなら問題ありません。 また、何か(ビルド上の不可解な)問題が出た場合、以下のような処理で復帰できると思います。
1) 画面定義の data 要素部分を削除してすぐ戻す
2) エラーが出るソースを開くと DataBindingUtil 部分のエラーがなくなる
3) binding.メソッドのエラーが残るので、プロジェクトをいったん閉じて開く
バインド用のクラスは自動作成されますが、画面定義したレイアウトファイル名から作成されます。 activity_main なら、ActivityMainBinding となります MainActivity.java
package sample.lightbox.myapplication;

import android.app.Activity;
import android.content.Context;
import android.databinding.BaseObservable;
import android.databinding.Bindable;
import android.databinding.DataBindingUtil;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ListView;

import sample.lightbox.myapplication.databinding.ActivityMainBinding;
import sample.lightbox.myapplication.databinding.MyitemBinding;


public class MainActivity extends Activity {

	TestArrayAdapter<Item> adapter = null;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);


		ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
		final User user = new User();
		user.setFirstName("山田");
		user.setLastName("タロウ");
		binding.setUser(user);

		final Item[] items = {
				new Item("あ","0001"),new Item("い","0002"),new Item("う","0003"),
				new Item("え","0004"),new Item("お","0005"),new Item("か","0006"),
				new Item("き","0007"),new Item("く","0008"),new Item("け","0009"),
				new Item("こ","0010")
		};

		adapter = new TestArrayAdapter<Item>(
				MainActivity.this,
				R.layout.myitem,
				new TestArrayAdapter.OnGetViewListener() {
					@Override
					public View onGetViewListener(int position, View convertView, ViewGroup parent) {

						MyitemBinding myitem;

						if ( convertView == null ) {
							LayoutInflater inflater = (LayoutInflater) MainActivity.this.getSystemService
								(Context.LAYOUT_INFLATER_SERVICE);
							myitem = DataBindingUtil.inflate(inflater, R.layout.myitem, parent, false);
						}
						else {
							myitem = DataBindingUtil.getBinding(convertView);
						}
						myitem.setItem(items[position]);

						// ここが重要です
						return myitem.getRoot();
					}
				}
		);

		adapter.addAll(items);
		((ListView)MainActivity.this.findViewById(R.id.listView)).setAdapter(adapter);

		// ボタンを押して、オブジェクトの変更のみで反映されます
		MainActivity.this.findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {
			@Override
			public void onClick(View v) {
				user.setFirstName("鈴木");
				user.setLastName("ジロウ");
				items[0].setText("getView での記述が簡単になりました");
				items[9].setText("しかも、オブジェクトを変更すると ListView も変わります");
			}
		});
	}

	// バインド用クラス(1)
	public static class User extends BaseObservable {
		private String firstName;
		private String lastName;

		@Bindable
		public String getFirstName() {
			return this.firstName;
		}
		@Bindable
		public String getLastName() {
			return this.lastName;
		}

		public void setFirstName(String firstName) {
			this.firstName = firstName;
			notifyPropertyChanged(sample.lightbox.myapplication.BR.firstName);
		}
		public void setLastName(String lastName) {
			this.lastName = lastName;
			notifyPropertyChanged(sample.lightbox.myapplication.BR.lastName);
		}
	}

	// バインド用クラス(2) : ListView 用
	public static class Item extends BaseObservable {
		private String title;
		private String text;
		private boolean flg = false;

		public Item(String text,String title) {
			this.text = text;
			this.title = title;
		}

		@Bindable
		public String getText() {
			return this.text;
		}
		@Bindable
		public String getTitle() {
			return this.title;
		}

		public void setText(String text) {
			this.text = text;
			notifyPropertyChanged(sample.lightbox.myapplication.BR.text);
		}

		public void seTitle(String title) {
			this.title = title;
			notifyPropertyChanged(sample.lightbox.myapplication.BR.title);
		}
	}

}

TestArrayAdapter は、getView をカスタマイズしやすくする為のクラスです。

activity_main.xml
<layout xmlns:android="http://schemas.android.com/apk/res/android">

	<data>
		<variable name="user" type="sample.lightbox.myapplication.MainActivity.User"/>
	</data>
        <LinearLayout
            android:orientation="vertical"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:padding="10dp">

		<TextView android:layout_width="match_parent"
				  android:layout_height="wrap_content"
				  android:textSize="26dp"
				  android:text="@{user.firstName}"
				  android:id="@+id/textView"/>
		<TextView android:layout_width="match_parent"
				  android:layout_height="wrap_content"
				  android:textSize="26dp"
				  android:text="@{user.lastName}"/>

		<Button
			android:layout_width="match_parent"
			android:layout_height="wrap_content"
			android:text="@string/button_1"
			android:id="@+id/button"/>

		<ListView
			android:layout_width="match_parent"
			android:layout_height="match_parent"
			android:id="@+id/listView"
			android:layout_below="@+id/button"/>

        </LinearLayout>

</layout>

myitem.xml
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
    <data>
        <variable name="item" type="sample.lightbox.myapplication.MainActivity.Item"/>
    </data>
    <LinearLayout
        android:orientation="vertical"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:padding="10dp">

        <TextView
            android:text="@{item.title}"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:id="@+id/textView3"/>

        <TextView
            android:text="@{item.text}"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:id="@+id/text1"
        />
    </LinearLayout>
</layout>

Module(app)用の gradle
android {
    compileSdkVersion 22
    buildToolsVersion "22.0.0"
    dataBinding {
        enabled = true
    }
    defaultConfig {
        applicationId "sample.lightbox.myapplication"
        minSdkVersion 19
        targetSdkVersion 22
        versionCode 1
        versionName "1.0"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
    productFlavors {
    }
}
モジュール用の build.gradle には、apply plugin: 'com.android.databinding' を追加します

データ部分は、Google Gson を使ってバインド用クラスに直接セットするようにするとさらに簡単になると思います。

自動作成されたクラス

Android デベロッパーの情報


posted by lightbox at 2016-09-27 20:27 | Comment(0) | Android Studio 2 | このブログの読者になる | 更新情報をチェックする

2016年08月08日


Android Studio で理解不能なエラーが出た時の対処方法 : Invalidate Caches / Restart



具体的には、Activity に当然あるはずのメソッドが『存在しません』等表示されて、赤い線でエラーが発生したりします。

その他にも、ありえないエラー等あった場合は、メニューから『Invalidate Caches / Restart』を実行します

復旧に数分かかる結構長い処理になりますが、過去4,5回発生したトラブルは皆この処理で解決しています。



posted by lightbox at 2016-08-08 18:57 | Comment(0) | Android Studio 2 | このブログの読者になる | 更新情報をチェックする

2016年08月01日


Android : TabHost のタブに下から上へのアニメーションを設定して、include で同一画面を使用するので 回転しないように AndroidMainfest で設定する

TabHost のタブの切り替わりがあまりにもそっけないので、slide_from_bottom.xml と slide_up.xml を作成して、下から上へと移動するアニメーションを設定しました。左右にしてもいいのですが、それだともう二種類のアニメーションが必要になりそうなので下から上にしました。

Interpolators の値(Android developer)
※ 参考にした記事

TabHost 内の各 TabSpec 内にある TextView の 端末回転時における保存と復帰 では、tab 毎に違った画面を定義しましたが、同じ画面を使用して工数が減るパターンとしてサンプルを作成しました。

ただ、この場合ですと、端末の回転による Activity の再作成で、Tab 内の EditText の保存と復帰が正しくされないので、AndroidMainfest で『回転しない』ように縦固定にしました。
public class MainActivity extends AppCompatActivity {

	private TabHost tabhost;
	private View pview;
	private View tabView1;
	private View tabView2;

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

		// TabHost のインスタンスを取得
		tabhost = (TabHost)MainActivity.this.findViewById(R.id.tabHost);
		// タブ追加の前に必要な初期処理
		tabhost.setup();

		// タブ用のインスタンスを作成
		TabHost.TabSpec tab1 = tabhost.newTabSpec("tab1");
		// タイトル文字列を設定
		tab1.setIndicator("タブ1の\nタイトル");
		// このタブ内に表示するコンテンツを TabHost 画面内の FrameLayout
		// の中にあるうちのコンテンツのひとつを設定
		tab1.setContent(R.id.linearLayout1);
		// TabHost に このタブを追加
		tabhost.addTab(tab1);

		TabHost.TabSpec tab2 = tabhost.newTabSpec("tab2");
		tab2.setIndicator("タブ2の\nタイトル");
		tab2.setContent(R.id.linearLayout2);
		tabhost.addTab(tab2);

		TabHost.TabSpec tab3 = tabhost.newTabSpec("tab3");
		tab3.setIndicator("タブ3の\nタイトル");
		tab3.setContent(R.id.linearLayout3);
		tabhost.addTab(tab3);

		pview = tabhost.getCurrentView();

		tabhost.setOnTabChangedListener(new TabHost.OnTabChangeListener() {
			@Override
			public void onTabChanged(String tabId) {
				View currentView = tabhost.getCurrentView();
				int tab = tabhost.getCurrentTab();

				Animation a_out = AnimationUtils.loadAnimation(MainActivity.this, R.anim.slide_up);
				Animation a_in = AnimationUtils.loadAnimation(MainActivity.this, R.anim.slide_from_bottom);

				pview.setAnimation(a_out);
				currentView.setAnimation(a_in);

				pview = currentView;
			}
		});

		// tab1 内のボタン
		tabView1 = MainActivity.this.findViewById(R.id.linearLayout1);
		Button button1;
		button1 = (Button) tabView1.findViewById(R.id.button);
		button1.setOnClickListener(new View.OnClickListener() {
			@Override
			public void onClick(View v) {

				TextView tv;
				// 画面最上部の TextView
				tv = (TextView) MainActivity.this.findViewById(R.id.textView);

				// 最初のタブの EditText
				EditText editText = (EditText) tabView1.findViewById(R.id.editText);
				tv.setText(editText.getText().toString());

				// tab1 のコンテンツを取得
				tv = (TextView) tabView1.findViewById(R.id.textView2);
				tv.setText(editText.getText().toString());

			}
		});

		// tab2 内のボタン
		tabView2 = MainActivity.this.findViewById(R.id.linearLayout2);
		Button button2;
		button2 = (Button) tabView2.findViewById(R.id.button);
		button2.setOnClickListener(new View.OnClickListener() {
			@Override
			public void onClick(View v) {

				TextView tv;

				// 真ん中のタブの EditText
				EditText editText = (EditText) tabView2.findViewById(R.id.editText);

				// tab2 のコンテンツを取得
				tv = (TextView) tabView2.findViewById(R.id.textView2);
				tv.setText(editText.getText().toString());

			}
		});

	}

}


画面定義
<?xml version="1.0" encoding="utf-8"?>
<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:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="lightbox.july.tabhostapplication.MainActivity">

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/textView"
        android:textSize="30dp"/>

    <TabHost
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/tabHost"
        android:layout_below="@+id/textView">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="vertical">

            <TabWidget
                android:id="@android:id/tabs"
                android:layout_width="match_parent"
                android:layout_height="wrap_content">
            </TabWidget>

            <FrameLayout
                android:id="@android:id/tabcontent"
                android:layout_width="match_parent"
                android:layout_height="match_parent">

                <include
                    android:id="@+id/linearLayout1"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    layout="@layout/tab_entry"/>

                <include
                    android:id="@+id/linearLayout2"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    layout="@layout/tab_entry"/>

                <include
                    android:id="@+id/linearLayout3"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    layout="@layout/tab_entry"/>

            </FrameLayout>
        </LinearLayout>
    </TabHost>
</RelativeLayout>

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:orientation="vertical"
              android:layout_width="match_parent"
              android:layout_height="match_parent">

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:textAppearance="?android:attr/textAppearanceLarge"
        android:id="@+id/textView2"
        android:textSize="40dp"/>

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="入力を表示"
        android:id="@+id/button"/>

    <EditText
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/editText"/>

</LinearLayout>

※ 便宜的に二つの画面をまとめて表示しています

AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest package="lightbox.july.tabhostapplication"
          xmlns:android="http://schemas.android.com/apk/res/android">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity
            android:name=".MainActivity"
            android:screenOrientation="portrait">
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>

                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>
        </activity>
    </application>

</manifest>


アニメーション
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android" android:interpolator="@android:anim/accelerate_interpolator">
    <translate android:fromYDelta="50%p" android:toYDelta="0" android:duration="160"/>
    <alpha android:fromAlpha="0.0" android:toAlpha="1.0" android:duration="160" />

</set>

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
    <translate android:fromYDelta="0" android:toYDelta="-50%p" android:duration="@android:integer/config_mediumAnimTime"/>
    <alpha android:fromAlpha="1.0" android:toAlpha="0.0" android:duration="200" />
</set>
※ 便宜上ひとつにして表示しています
※ 上が、slide_from_bottom.xml です


posted by lightbox at 2016-08-01 20:22 | Comment(0) | Android Studio 2 | このブログの読者になる | 更新情報をチェックする

Android : TabHost 内の各 TabSpec 内にある TextView の 端末回転時における保存と復帰

TabHost そのものは、ひとつの Activity で処理する場合とても簡単ですが、Android Studio の デザインで各タブの画面を確認できるように、xml で3つの画面定義をして activity_main.xml で include で読み込んでいます。

include タグにはそれぞれ id を設定して、TabSpec のコンテンツとして渡せるようにもしてあります。

端末回転時の保存と復帰に関しても、一般的な onSaveInstanceState と onRestoreInstanceState を使用していますが、画面内のコンテンツの id のパターンを以下のようにして、サンプルコードを作成しています。

1) EditText と Button には全て違った id を設定する
2) TextView には同じ id を設定する

通常、EditText は内部で自動的に内容が保存されて復帰されますが、3つの inclide 内の id を同じにしてしまうと( つまり、ひとつの画面定義で3つの include を使いまわししてしまうと )、保存と復帰がうまくいかないようなので、3つの画面を作成して、別々の id を設定しています。

しかし、同じ id を持った画面を使いまわす事も想定して、TextView には同じ id を設定して、include の id でまず view を取得して、その中の TextView として取得するサンプルコードです。


ここでは、使用していませんが最近の Drawable の取得方法として
Drawable drawable = ResourcesCompat.getDrawable(getResources(), R.drawable.□□□, null);

そして、Tab にアイコンを表示させる方法について
Icon in Tab is not showing up (StackOverflow)
※ 要するにカスタムなインジケータを作成するそうです
public class MainActivity extends AppCompatActivity {

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

		// TabHost のインスタンスを取得
		TabHost tabhost = (TabHost)MainActivity.this.findViewById(R.id.tabHost);
		// タブ追加の前に必要な初期処理
		tabhost.setup();

		// タブ用のインスタンスを作成
		TabHost.TabSpec tab1 = tabhost.newTabSpec("tab1");
		// タイトル文字列を設定
		tab1.setIndicator("タブ1の\nタイトル");
		// このタブ内に表示するコンテンツを TabHost 画面内の FrameLayout
		// の中にあるうちのコンテンツのひとつを設定
		tab1.setContent(R.id.linearLayout1);
		// TabHost に このタブを追加
		tabhost.addTab(tab1);

		TabHost.TabSpec tab2 = tabhost.newTabSpec("tab2");
		tab2.setIndicator("タブ2の\nタイトル");
		tab2.setContent(R.id.linearLayout2);
		tabhost.addTab(tab2);

		TabHost.TabSpec tab3 = tabhost.newTabSpec("tab3");
		tab3.setIndicator("タブ3の\nタイトル");
		tab3.setContent(R.id.linearLayout3);
		tabhost.addTab(tab3);

		// tab1 内のボタン
		Button button1;
		button1 = (Button) MainActivity.this.findViewById(R.id.button);
		button1.setOnClickListener(new View.OnClickListener() {
			@Override
			public void onClick(View v) {

				TextView tv;
				// 画面最上部の TextView
				tv = (TextView) MainActivity.this.findViewById(R.id.textView);

				// 最初のタブの EditText
				EditText editText = (EditText) MainActivity.this.findViewById(R.id.editText);
				tv.setText(editText.getText().toString());

				// tab1 のコンテンツを取得
				View tab1 = MainActivity.this.findViewById(R.id.linearLayout1);
				tv = (TextView) tab1.findViewById(R.id.textView2);
				tv.setText(editText.getText().toString());

			}
		});

		// tab2 内のボタン
		Button button2;
		button2 = (Button) MainActivity.this.findViewById(R.id.button2);
		button2.setOnClickListener(new View.OnClickListener() {
			@Override
			public void onClick(View v) {

				TextView tv;

				// 真ん中のタブの EditText
				EditText editText = (EditText) MainActivity.this.findViewById(R.id.editText2);

				// tab2 のコンテンツを取得
				View tab2 = MainActivity.this.findViewById(R.id.linearLayout2);
				tv = (TextView) tab2.findViewById(R.id.textView2);
				tv.setText(editText.getText().toString());

			}
		});


	}

	@Override
	protected void onRestoreInstanceState(Bundle savedInstanceState) {
		super.onRestoreInstanceState(savedInstanceState);

		TabHost tabhost = (TabHost)findViewById(R.id.tabHost);

		// タブ位置の復帰
		int currentTab =  savedInstanceState.getInt("CurrentTab");
		if ( currentTab != 0l ) {
			tabhost.setCurrentTab(currentTab);
		}
		else {
			tabhost.setCurrentTab(0);
		}

		// 一番上の TextView を復帰
		String textTop = savedInstanceState.getString("TextViewTop");
		if (textTop != null ) {
			TextView tv;
			tv = (TextView) MainActivity.this.findViewById(R.id.textView);
			tv.setText(textTop);
		}

		// tab1 内の TextView を復帰
		String text1 = savedInstanceState.getString("TextView1");
		if (text1 != null ) {
			View tab1 = MainActivity.this.findViewById(R.id.linearLayout1);
			TextView tv = (TextView) tab1.findViewById(R.id.textView2);
			tv.setText(text1);
		}

		// tab2 内の TextView を復帰
		String text2 = savedInstanceState.getString("TextView2");
		if (text2 != null ) {
			View tab2 = MainActivity.this.findViewById(R.id.linearLayout2);
			TextView tv = (TextView) tab2.findViewById(R.id.textView2);
			tv.setText(text2);
		}

	}

	@Override
	protected void onSaveInstanceState(Bundle outState) {
		super.onSaveInstanceState(outState);

		// タブ位置の保存
		TabHost tabhost = (TabHost)MainActivity.this.findViewById(R.id.tabHost);
		int currentTab = tabhost.getCurrentTab();
		outState.putInt("CurrentTab", currentTab);

		// 一番上の TextView を保存
		TextView tvTop = (TextView) MainActivity.this.findViewById(R.id.textView);
		outState.putString("TextViewTop", tvTop.getText().toString());

		// tab1 のコンテンツを取得
		View tab1 = MainActivity.this.findViewById(R.id.linearLayout1);
		TextView tv1 = (TextView) tab1.findViewById(R.id.textView2);
		outState.putString("TextView1", tv1.getText().toString());

		// tab2 のコンテンツを取得
		View tab2 = MainActivity.this.findViewById(R.id.linearLayout2);
		TextView tv2 = (TextView) tab2.findViewById(R.id.textView2);
		outState.putString("TextView2", tv2.getText().toString());

	}
}


画面定義
<?xml version="1.0" encoding="utf-8"?>
<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:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="lightbox.july.tabhostapplication.MainActivity">

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/textView"
        android:textSize="30dp"/>

    <TabHost
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/tabHost"
        android:layout_below="@+id/textView">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="vertical">

            <TabWidget
                android:id="@android:id/tabs"
                android:layout_width="match_parent"
                android:layout_height="wrap_content">
            </TabWidget>

            <FrameLayout
                android:id="@android:id/tabcontent"
                android:layout_width="match_parent"
                android:layout_height="match_parent">

                <include
                    android:id="@+id/linearLayout1"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    layout="@layout/tab_entry"/>

                <include
                    android:id="@+id/linearLayout2"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    layout="@layout/tab_entry2"/>

                <include
                    android:id="@+id/linearLayout3"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    layout="@layout/tab_entry3"/>

            </FrameLayout>
        </LinearLayout>
    </TabHost>
</RelativeLayout>


3つの tab 毎の定義
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:orientation="vertical"
              android:layout_width="match_parent"
              android:layout_height="match_parent">

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:textAppearance="?android:attr/textAppearanceLarge"
        android:id="@+id/textView2"
        android:textSize="40dp"/>

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="入力を表示"
        android:id="@+id/button"/>

    <EditText
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/editText"/>

</LinearLayout>

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:orientation="vertical"
              android:layout_width="match_parent"
              android:layout_height="match_parent">

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:textAppearance="?android:attr/textAppearanceLarge"
        android:id="@+id/textView2"
        android:textSize="40dp"/>

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="入力を表示"
        android:id="@+id/button2"/>

    <EditText
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/editText2"/>

</LinearLayout>

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:orientation="vertical"
              android:layout_width="match_parent"
              android:layout_height="match_parent">

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:textAppearance="?android:attr/textAppearanceLarge"
        android:id="@+id/textView2"
        android:textSize="40dp"/>

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="なにもしない"
        android:id="@+id/button3"/>

    <EditText
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/editText3"/>

</LinearLayout>
※ 便宜上3つにまとめていますが、xml ファイルはそれぞれ 作成しています



posted by lightbox at 2016-08-01 13:40 | Comment(0) | Android Studio 2 | このブログの読者になる | 更新情報をチェックする

2016年07月26日


Android の 端末回転時の EditText と TextView の違い

そもそも、画面を回転させなければ良いと思うのですが、『回転』して、Activity が再作成されたにもかかわらず、EditText の内容が保持されるので、インターネットを探して、けっこうやっと見つけました。

[Android]画面回転時の挙動

結局、EditText 内で保存と復帰を行っているそうです(ソースを読まれたそうなので)。

この記事内では、『Parcelable』が良く解らないというくだりがありますが、こちらは Android 関係では良くお世話になる Y.A.M さんの記事にあります。

Android Parcelable を使ってクラスのメンバを一時保存

まあ、自分的には教える立場にあるので、できるだけ難解な通路は通りたく無いので Google Gson で 文字列で保存して、デシリアライズして復帰したいところです。
Android 関連の情報は、インターネットで正確な情報を引っこ抜くのはかなり難易度が高く、整理できても結局初心者に伝えるほうに無理があるという結論に良く達します。
さらに、回転しても Activity 破棄させない設定等もありますが、汎用的な一般向けアプリを作るプロでも無いのに必要とはとても思え無い内容です。結局全ての場合を検証した事を確認できるような記事は表には無いですし・・・ やはり、回転させないのが一番です。 そして、それを前提にして回転させた場合に消失する TextView の復帰方法としての onSaveInstanceState(Bundle) で保存し、 onRestoreInstanceState(Bundle) で読み出します。 を目で見てを確かめるのがいいと思います。 ちなみに、TabHost の中身を同じ画面で include したら、最後のタブの EditText の内容が全てのタブの EditText に復帰されました。 別々の画面だと、当然それぞれが正しく保存されます
posted by lightbox at 2016-07-26 21:53 | Comment(0) | Android Studio 2 | このブログの読者になる | 更新情報をチェックする

2016年07月25日


ViewPager 内のイベントで設定した TextView の値を保持する Fragment 処理



Fragment の中の onCreateView で表示する為に、ボタンをクリックした時に TextView にセットすると同時に Fragment の中に定義した static な変数にも保存しました。保存された値は、adapter から getItem で呼ばれた時に渡す新たなインスタンスを作成する時に、再度 putStringArray でセットし setArguments でフラグメントに保存されます。

このような処理をインターネットで探しても無かったので、こうしてみましたが一応は動いています。このベースは、Android Studio のテンプレートなので、getArguments().getInt(PAGE_POSTION) をいきなり使うのは問題無いと思います( テンプレートでそうなっていたので )。なので、その付加情報として配列を追加した形です。
MainActivity.java
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentPagerAdapter;
import android.support.v4.view.ViewPager;
import android.support.v7.app.AppCompatActivity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {

	private SectionsPagerAdapter adapter;
	private ViewPager view_pager;

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

		adapter = new SectionsPagerAdapter(getSupportFragmentManager());

		view_pager = (ViewPager) findViewById(R.id.viewPager);
		view_pager.setOffscreenPageLimit(1);
		view_pager.setAdapter(adapter);

	}

	public class SectionsPagerAdapter extends FragmentPagerAdapter {

		public SectionsPagerAdapter(FragmentManager fm) {
			super(fm);
		}

		@Override
		public Fragment getItem(int position) {
			return PlaceholderFragment.newInstance(position);
		}

		@Override
		public int getCount() {
			return 4;
		}

		@Override
		public CharSequence getPageTitle(int position) {
			switch (position) {
				case 0:
					return "SECTION 1";
				case 1:
					return "SECTION 2";
				case 2:
					return "SECTION 3";
				case 3:
					return "SECTION 4";
			}
			return null;
		}
	}

	public static class PlaceholderFragment extends Fragment {

		private static final String PAGE_POSTION = "page_position";
		private static final String SAVE_VIEW_TEXT = "save_view_text";
		private static String text[] = new String[4];

		public PlaceholderFragment() {
		}

		public static PlaceholderFragment newInstance(int pagePosition) {

			PlaceholderFragment fragment = new PlaceholderFragment();
			Bundle args = new Bundle();
			args.putInt(PAGE_POSTION, pagePosition);
			args.putStringArray("save_view_text", text);
			fragment.setArguments(args);

			return fragment;
		}

		@Override
		public View onCreateView(LayoutInflater inflater, ViewGroup container,
								 Bundle savedInstanceState) {

			final View rootView = inflater.inflate(R.layout.fragment_main, container, false);
			if (getArguments().getStringArray(SAVE_VIEW_TEXT)[getArguments().getInt(PAGE_POSTION)] != null) {
				TextView textView = (TextView) rootView.findViewById(R.id.section_label);
				textView.setText(getArguments().getStringArray(SAVE_VIEW_TEXT)[getArguments().getInt(PAGE_POSTION)]);
			}

			Button btn = (Button) rootView.findViewById(R.id.button);
			btn.setOnClickListener(new View.OnClickListener() {
				@Override
				public void onClick(View v) {
					EditText et = (EditText) rootView.findViewById(R.id.editText);
					TextView textView = (TextView) rootView.findViewById(R.id.section_label);
					textView.setText(et.getText().toString());
					text[getArguments().getInt(PAGE_POSTION)] = et.getText().toString();
				}
			});

			return rootView;
		}
	}


}

setOffscreenPageLimit(1) を 3 にすれば、アクティビティが破棄されない場合は TextView の値を保持してくれます。ですが、メモリを無駄に使うと思うので、Fragment の setArguments で渡すほうがいいと思います。

activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<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:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="lightbox.july.viewpagerapplication.MainActivity">

    <android.support.v4.view.ViewPager
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/viewPager"
        android:layout_alignParentTop="true"
        android:layout_alignParentStart="true">
        <android.support.v4.view.PagerTitleStrip
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:id="@+id/view"/>
    </android.support.v4.view.ViewPager>

</RelativeLayout>

PagerTitleStrip は、特別必要ではありませんが、ViewPager のタイトルをせっかく処理しているので使用しています。設置は、手作業で ViewPager の間に挟む必要がありました。カスタムビューなので、コンポーネントツリーではうまくいきませんでした。



fragment_main.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
                android:layout_width="match_parent"
                android:layout_height="match_parent">

    <TextView
        android:id="@+id/section_label"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:textSize="40dp"
        android:layout_marginBottom="10dp"/>

    <EditText
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/editText"
        android:layout_below="@+id/section_label"
        android:layout_alignParentStart="true"/>

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="New Button"
        android:id="@+id/button"
        android:layout_below="@+id/editText"
        android:layout_alignParentStart="true"/>

</RelativeLayout>






結局これを使えば、わりと簡単に横スクロールのページ処理ができますが、Android の テンプレートでは、FloatingActionButton が使用されていたので、それを使うとページ先頭とページ最後へのジャンプも簡単に実装できます。

※ FloatingActionButton は、com.android.support:design を追加する必要があります



Snackbar によるイベント処理
		FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
		fab.setOnClickListener(new View.OnClickListener() {
			@Override
			public void onClick(View view) {
				Snackbar.make(view, "ページ移動", Snackbar.LENGTH_LONG)
					.setAction("先頭", new View.OnClickListener() {
						@Override
						public void onClick(View v) {
							mViewPager.setCurrentItem(0);
						}
					})
					.show();
			}
		});

		FloatingActionButton fab2 = (FloatingActionButton) findViewById(R.id.fab2);
		fab2.setOnClickListener(new View.OnClickListener() {
			@Override
			public void onClick(View view) {
				Snackbar.make(view, "ページ移動", Snackbar.LENGTH_LONG)
					.setAction("最後", new View.OnClickListener() {
						@Override
						public void onClick(View v) {
							mViewPager.setCurrentItem(3);
						}
					})
					.show();
			}
		});




posted by lightbox at 2016-07-25 18:53 | Comment(0) | Android Studio 2 | このブログの読者になる | 更新情報をチェックする

2016年07月19日


WebView で JavaScript にデータを渡したい時に注意する事

Android と WebView とのやり取りは、@JavascriptInterface で定義される public なメソッドですが、JavaScript から 呼び出されたそれらのメソッドは、UI スレッドでは無いところが重要です。

特に問題の無い JavaScript => Android

JavaScript からデータを JSON 文字列で渡せば、たいていの情報を簡単に渡す事ができ、受け取った Android のメソッドでは Google Gson でデシリアライズすれば、Handler で post してそのまま画面に対して有効利用できるはすです。この時、JavaScript で実行されたアクションから、非同期で画面へアクセスしますが、矛盾のないデータの受け渡しとなります。

非同期の為、return できない Android => JavaScript

しかし、JavaScript の呼び出しで Android が値を返す場合は、UI スレッドへのアクセスが終わる前に戻り地を返す必要が出てきます。つまり、そもそも画面の値を JavascriptInterface を使って JavaScript に戻すには、JavaScript から Android のメソッドを呼び出す前に、画面の値を前もって取得しておく必要があります。

loadUrl で二段構えにする

もちろん、loadUrl で直接値を渡せば、こんな事をする必要はありませんが、文字列の中に文字列を埋め込む作業となるので良いものとは言えません。そこで、Android からは JavaScript を呼び出すだけにして、それを行う前に画面のデータを取得しておきます。そうすれば、必ず JavaScript に今のデータが JSON 文字列で渡る事となります。

Android 側
package lightbox.july.webviewjavascript;

import android.graphics.Bitmap;
import android.os.Bundle;
import android.os.Handler;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import android.webkit.JavascriptInterface;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.Button;
import android.widget.EditText;

import com.google.gson.Gson;

public class MainActivity extends AppCompatActivity {

	private WebView webview;
	private String startPage;
	private JsonDataList jdl = null;
	private Handler handler;
	private Gson gson;


	@Override
	protected void onStop() {
		super.onStop();
		webview.clearHistory();
		webview.clearCache(true);

		Log.i("lightbox", "onStop");

	}

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

		gson = new Gson();
		handler = new Handler();

		Button button1 = (Button)MainActivity.this.findViewById(R.id.button);
		button1.setOnClickListener(new View.OnClickListener() {
			@Override
			public void onClick(View v) {

				if ( jdl == null ) {
					jdl = gson.fromJson("{\"item\": [\n" +
						"        {" +
						"            \"text\": \"\"," +
						"            \"subject\": \"\"," +
						"            \"name\": \"\"," +
						"            \"datetime\": \"\"" +
						"        }]}",JsonDataList.class);
				}

				EditText et;
				et = (EditText) MainActivity.this.findViewById(R.id.editSubject);
				jdl.item[0].subject = et.getText().toString();
				et = (EditText) MainActivity.this.findViewById(R.id.editText);
				jdl.item[0].text = et.getText().toString();

				webview.loadUrl("javascript:getAndroidData()");

			}
		});
		button1.setAllCaps(false);

		Button button2 = (Button)MainActivity.this.findViewById(R.id.button2);
		button2.setOnClickListener(new View.OnClickListener() {
			@Override
			public void onClick(View v) {

				webview.loadUrl("javascript:getBoardData()");

			}
		});
		button2.setAllCaps(false);

		webview = (WebView) MainActivity.this.findViewById(R.id.webView);
		JavaScriptAccess jsa = new JavaScriptAccess();
		// **********************************
		// 第二引数の文字列が、WebView の中のページの
		// JavaScript より使用可能になります
		// **********************************
		webview.addJavascriptInterface(jsa,"myAndroidObject");
		// 必ず必要な JavaScript を有効にする設定
		webview.getSettings().setJavaScriptEnabled(true);
		// 必ず必要な設定
		webview.setWebViewClient(new WebViewClient(){

			// 必ず必要な設定 : 常に WebView 内でページを表示する為
			@Override
			public boolean shouldOverrideUrlLoading(WebView view, String url) {
				return super.shouldOverrideUrlLoading(view, url);
			}

			// オプション : 表示されたページの URL を変数にセット
			@Override
			public void onPageStarted(WebView view, String url, Bitmap favicon) {
				super.onPageStarted(view, url, favicon);

				Log.i("lightbox", "onPageStarted:" + url);
				startPage = url;

			}

			// オプション : ページを表示し終わってから発生するイベント
			@Override
			public void onPageFinished(WebView view, String url) {
				super.onPageFinished(view, url);

			}
		});

		// 目的のページを表示する
		webview.loadUrl("https://lightbox.sakura.ne.jp/homepage/demo/data/csvtype/android.html");

	}

	// **********************************
	// JavaScript 用クラス
	// **********************************
	private class JavaScriptAccess {

		@JavascriptInterface
		public void setAndroid(String json) {

			Log.i("lightbox","setAndroid");

			jdl = gson.fromJson(json,JsonDataList.class);

			try {
				// 別スレッドから UI スレッドへのアクセス
				handler.post(new Runnable() {
					@Override
					public void run() {
						EditText et;
						et = (EditText) MainActivity.this.findViewById(R.id.editSubject);
						et.setText(jdl.item[0].subject);
						et = (EditText) MainActivity.this.findViewById(R.id.editText);
						et.setText(jdl.item[0].text);

					}
				});

			} catch (Exception e) {
				e.printStackTrace();
			}

		}

		@JavascriptInterface
		public String getAndroid() {

			Log.i("lightbox","getAndroid");
			String json = gson.toJson(jdl.item[0],JsonData.class);

			return json;
		}

	}

	private class JsonData  {

		String text;
		String subject;
		String name;
		String datetime;

	}

	private class JsonDataList {
		JsonData[] item;
	}
}


JavaScript 側
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<style>
* {
	font-size:18px;
}
div {
	white-space: pre-wrap;
	word-wrap: break-word;
}
</style>
<script src="//ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script>
function getBoardData() {
	// ***************************************
	// Android WebView の中のこのページ
	// より、サーバへアクセスします
	// ***************************************
	$.get("json.php",function( data ){
		if ( typeof myAndroidObject !== 'undefined' ) {
			// サーバから受け取った application/json データ
			// を文字列に変換して Android に渡します
			myAndroidObject.setAndroid(JSON.stringify(data));
		}
		else {
			// 通常のブラウザではこちらが実行されます
			console.log(JSON.stringify(data,null,"   "));
		}
	})
}

function getAndroidData() {

	if ( typeof myAndroidObject !== 'undefined' ) {
		var json = myAndroidObject.getAndroid();
		$("#json").text(json);
	}
	else {
		// 通常のブラウザではこちらが実行されます
		console.log("Android から実行されていません");
	}

}

$( function(){
	$("#set_android").on("click",function(){
		getBoardData();
	});

} );
</script>
</head>
<body>
<input type="button" id="set_android" value="Android へデータを送る">
<br>
<br>
<div id="json">
</div>
</body>
</html>



画面定義
<?xml version="1.0" encoding="utf-8"?>
<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:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="lightbox.july.webviewjavascript.MainActivity">

    <ScrollView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/scrollView">

        <LinearLayout
            android:orientation="vertical"
            android:layout_width="match_parent"
            android:layout_height="match_parent">

            <Button
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:text="Android からデータを取得"
                android:id="@+id/button"/>

            <Button
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:text="Android へデータを送る"
                android:id="@+id/button2"/>

            <EditText
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:id="@+id/editSubject"/>

            <EditText
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:id="@+id/editText"/>

            <WebView
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:id="@+id/webView"/>
        </LinearLayout>
    </ScrollView>

</RelativeLayout>




posted by lightbox at 2016-07-19 15:23 | Comment(0) | Android Studio 2 | このブログの読者になる | 更新情報をチェックする

2016年07月11日


ExpandableListView を使用して、タップした時に明細データ表示する

本来は、2階層構造のデータを扱います。例えば、最初に一覧として表示されるのが掲示板のスレッドだとすると、タップするとその下にそのスレッドに投稿されたタイトルの一覧が開く、と言うような用途です。

ただ、そのようなテストデータを作成するのは結構面倒なので、まずは単純に掲示板の一つのスレッドのタイトル一覧を表示して、タップするとさらに投稿者と日付と本文を表示するようにしました。



ExpandableListView にデータを表示するには、専用のアダプタか必要ですが、ここでは、abstract class BaseExpandableListAdapter を使用しています。ExpandableListView にこのアダプターを継承したクラスのインスタンスを引き渡すと、少なくとも、getGroupView と getChildView と getGroupCount と getChildrenCount を呼び出して正しく表示してくれます。

とても簡単に情報を多く効果的に読み取る事のできるリストビューを作成する事ができます。

Android Studio で BaseExpandableListAdapter を継承してクラスを作成すると、中が空の状態では赤い波線が表示されてエラーになります。しかし、この赤い波線をクリックすると現れる左側の赤いランプをクリックしてやると、以下のようになります。


ここで、implement methods を選択してやると、ダイアログで必要なメソッドが選択済みになるので、 OK すると展開されます。そして、最低限 getGroupView と getChildView と getGroupCount と getChildrenCount を実装して、必要ならば getGroup と getChild を実装して中で使用すると良いと思います。

MainActivity 部分
package lightbox.july.expandablelistviewsimpledata;

import android.content.Context;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseExpandableListAdapter;
import android.widget.ExpandableListView;
import android.widget.TextView;

import com.google.gson.Gson;

import jp.android.work.Tools;

public class MainActivity extends AppCompatActivity {

	private JsonDataList json;
	private MyExpandableListAdapter adapter;
	private ExpandableListView elv;

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

		// 開くリストビューのインスタンス
		elv = (ExpandableListView) MainActivity.this.findViewById(R.id.expandableListView);

		// JSON データ(文字列)
		String data_url = "https://lightbox.sakura.ne.jp/homepage/demo/data/csvtype/json.php";
		Tools.callHttpGet(data_url, "utf-8", new Tools.OnAsyncTaskListener() {
			@Override
			public void onAsyncTaskListener(String s) {
				Gson gson = new Gson();
				// デシリアライズ
				json =  gson.fromJson(s, JsonDataList.class);
				adapter = new MyExpandableListAdapter(json);
				elv.setAdapter(adapter);

			}
		});

	}

	// 開くリストビュー用のアダプタ
	private class MyExpandableListAdapter extends BaseExpandableListAdapter{

		private JsonDataList json;

		public MyExpandableListAdapter(JsonDataList json) {
			this.json= json;
		}

		// ******************************
		// 親データの表示
		// ( 実際は データの subject )
		// ******************************
		@Override
		public View getGroupView(int groupPosition, boolean isExpanded, View convertView, ViewGroup parent) {
			View rowView = convertView;
			if (rowView == null) {
				// 現在の View の取得
				LayoutInflater inflater = (LayoutInflater) MainActivity.this.getSystemService
					(Context.LAYOUT_INFLATER_SERVICE);
				rowView = inflater.inflate(R.layout.group_view, null);
			}

			// Adapter にセットされているこのポジションの MyData を取得
			JsonData data = (JsonData)adapter.getGroup(groupPosition);

			// group_view にデータをセット
			TextView tv = (TextView) rowView.findViewById(R.id.textView);
			tv.setText(data.subject);

			return rowView;
		}

		// ******************************
		// 子データの表示
		// ( 実際は データの 残りの項目 )
		// ******************************
		@Override
		public View getChildView(int groupPosition, int childPosition, boolean isLastChild, View convertView, ViewGroup parent) {
			View rowView = convertView;
			if (rowView == null) {
				// 現在の View の取得
				LayoutInflater inflater = (LayoutInflater) MainActivity.this.getSystemService
					(Context.LAYOUT_INFLATER_SERVICE);
				rowView = inflater.inflate(R.layout.child_view, null);
			}

			// Adapter にセットされているこのポジションの MyData を取得
			JsonData data = (JsonData)adapter.getGroup(groupPosition);

			// child_view にデータをセット
			TextView tv2 = (TextView) rowView.findViewById(R.id.textView2);
			tv2.setText(data.name);
			TextView tv3 = (TextView) rowView.findViewById(R.id.textView3);
			tv3.setText(data.datetime);
			TextView tv4 = (TextView) rowView.findViewById(R.id.textView4);
			tv4.setText(data.text);

			return rowView;
		}

		@Override
		public int getGroupCount() {
			return json.item.length;
		}

		@Override
		public int getChildrenCount(int groupPosition) {
			return 1;
		}

		@Override
		public Object getGroup(int groupPosition) {
			return json.item[groupPosition];
		}

		@Override
		public Object getChild(int groupPosition, int childPosition) {
			return null;
		}

		@Override
		public long getGroupId(int groupPosition) {
			return 0;
		}

		@Override
		public long getChildId(int groupPosition, int childPosition) {
			return 0;
		}

		@Override
		public boolean hasStableIds() {
			return false;
		}

		@Override
		public boolean isChildSelectable(int groupPosition, int childPosition) {
			return false;
		}
	}

	private class JsonData  {

		String text;
		String subject;
		String name;
		String datetime;

	}

	private class JsonDataList {
		JsonData[] item;
	}

}


全てのソースコード


posted by lightbox at 2016-07-11 20:52 | Comment(0) | Android Studio 2 | このブログの読者になる | 更新情報をチェックする

2016年06月26日


カスタム・リストビュー・ダイアログ : DialogFragment 内の ListView を ArrayAdapter でカスタムする



結果的には、DialogFragment を使用しない場合より簡単に実装できました。正直これで正しいのかどうかは解りませんが、ダイアログのインスタンスはシステムで再作成される事を前提にしているので、DialogFragment 内の onCreateDialog で全て実装する事によって、再度 View をセットするというような処理は必要ありません。但し、行を選択して閉じる時に、dismiss() によってダイアログを終わらせています。

リストビューのデータは、本来 newInstance で put すべきでしょうが、onCreateDialog 内で全て完結するほうがスッキリしているのでそうしました。

行のクリックでは、View からデータを取得していますが、MyData に何かメソッドを実装して実行したい場合は、
MyAdapter adapter = (MyAdapter) parent.getAdapter();
MyData my_data = (MyData) adapter.getItem(position);
で問題無いと思います MainActivity 内のソースコード
@Override
protected void onCreate(Bundle savedInstanceState) {
	super.onCreate(savedInstanceState);
	setContentView(R.layout.activity_main);

	// *********************************
	// ボタンをクリック
	// ( ダイアログフラグメント で表示 )
	// *********************************
	MainActivity.this.findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {
		@Override
		public void onClick(View v) {

			 MyDialogFragment my_dialog_fragment = MyDialogFragment.newInstance("地域一覧");

			// ダイアログの表示
			// ※ この環境では、getSupportFragmentManager が必要です
			my_dialog_fragment.show(getSupportFragmentManager(),"dialog");

		}
	});

}

// *********************************
// static クラスの定義
// 1) DialogFragment
// 2) ArrayAdapter
// 3) MyData
// *********************************
public static class MyDialogFragment extends DialogFragment {

	// ユーザが呼び出す為のもの( 外側で処理しても良い )
	public static MyDialogFragment newInstance(String title) {
		// インスタンス作成
		MyDialogFragment my_dialog_fragment = new MyDialogFragment();
		// システムに引数を保存
		Bundle args = new Bundle();
		args.putString("title", title);
		my_dialog_fragment.setArguments(args);
		return my_dialog_fragment;
	}

	// *********************************
	// ここが、ダイアログの作成時に呼ばれます
	// 初回はユーザがダイアログを作成
	// デバイスを横にした時はシステムが
	// ダイアログを作成します
	// *********************************
	@Override
	public Dialog onCreateDialog(Bundle savedInstanceState) {

		// この中で使用するダイアログ作成用
		final AlertDialog.Builder ad_builder_in_fragment;

		// この DialogFragment に保存されているデータを取得
		// ( システムが再作成した場合でも Bundle よりデータを取得できます )
		String title = MyDialogFragment.this.getArguments().getString("title");

		// DialogFragment から Activity を取得して使う
		// ( ダイアログが使われる Activity を MyDialogFragment 内で取得しています )
		ad_builder_in_fragment = new AlertDialog.Builder(MyDialogFragment.this.getActivity());
		// タイトル設定
		ad_builder_in_fragment.setTitle(title);
		ad_builder_in_fragment.setNegativeButton("キャンセル", new DialogInterface.OnClickListener() {
			@Override
			public void onClick(DialogInterface dialog, int which) {
				// キャンセル
			}
		});
		// *********************************
		// ダイアログのメインビューに
		// リストビューを設定
		// *********************************
		LayoutInflater inflater = (LayoutInflater) MyDialogFragment.this.getActivity().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
		ListView list_view = (ListView) inflater.inflate(R.layout.listview, null);
		// *********************************
		// 行をクリックした時のイベント
		// *********************************
		list_view.setOnItemClickListener(new AdapterView.OnItemClickListener() {
			@Override
			public void onItemClick(AdapterView<?> parent, View view, int position, long id) {

				// View から行データを取得
				TextView tv;
				tv = (TextView) view.findViewById(R.id.textPlace);
				String textPlace = tv.getText().toString();
				tv = (TextView) view.findViewById(R.id.textCode);
				String textCode = tv.getText().toString();

				// メッセージの作成
				String message = String.format("%s : %s", textPlace, textCode);
				// logcat に表示
				Log.i("lightbox", message );
				// Toast で表示
				Toast.makeText(MyDialogFragment.this.getActivity(),message, Toast.LENGTH_SHORT).show();
				// TextView に表示
				tv = (TextView) MyDialogFragment.this.getActivity().findViewById(R.id.textView);
				tv.setText(message);

				// 破棄
				MyDialogFragment.this.dismiss();
			}
		});
		// *********************************
		// カスタム・アダプター
		// *********************************
		MyAdapter adapter = new MyAdapter(MyDialogFragment.this.getActivity(),R.layout.listview_item);

		// *********************************
		// ListView に表示するデータ
		// *********************************
		List<MyData> list = new ArrayList<MyData>();
		list.add(new MyData("大阪","27"));
		list.add(new MyData("東京","13"));
		list.add(new MyData("岡山","33"));
		list.add(new MyData("北海道","1"));
		list.add(new MyData("沖縄","47"));
		list.add(new MyData("京都","26"));
		list.add(new MyData("滋賀","25"));

		// *********************************
		// 新しく領域確保して配列データを作成する
		// *********************************
		MyData[] my_data = list.toArray( new MyData[0] );

		// *********************************
		// カスタム・アダプターにデータをセット
		// *********************************
		adapter.addAll(my_data);
		// *********************************
		// ListView にカスタム・アダプターをセット
		// *********************************
		list_view.setAdapter(adapter);

		ad_builder_in_fragment.setView(list_view);

		// ダイアログを作成してシステムに返す
		return ad_builder_in_fragment.create();
	}
}

// *********************************
// MyData 専用アダブター
// *********************************
public static class MyAdapter extends ArrayAdapter {

	public MyAdapter(Context context, int resource) {
		super(context, resource);
	}

	@Override
	public View getView(int position, View convertView, ViewGroup parent) {

		View rowView = convertView;
		if (rowView == null) {
			// 現在の View の取得
			LayoutInflater inflater = (LayoutInflater) getContext().getSystemService
				(Context.LAYOUT_INFLATER_SERVICE);
			rowView = inflater.inflate(R.layout.listview_item, null);
		}

		MyData my_data = (MyData)this.getItem(position);

		TextView tv;
		// listview_item 内の textPlace にデータをセット
		tv = (TextView) rowView.findViewById(R.id.textPlace);
		tv.setText(my_data.toString());
		// listview_item 内の textCode にデータをセット
		tv = (TextView) rowView.findViewById(R.id.textCode);
		tv.setText(my_data.getValue());

		return rowView;
	}
}

// *********************************
// MyData
// *********************************
public static class MyData {

	private String myString;
	private String myValue;

	public MyData(String myString,String myValue) {
		this.myString = myString;
		this.myValue = myValue;
	}

	// データ取得用
	String getValue() {
		return myValue;
	}

	@Override
	public String toString() {
		return myString;
	}
}


※ 全てのソースコードはこちらにあります




posted by lightbox at 2016-06-26 01:33 | Comment(0) | Android Studio 2 | このブログの読者になる | 更新情報をチェックする

カスタム・リストビュー・ダイアログ : ダイアログ内の ListView を ArrayAdapter でカスタムする


※ この処理には、DialogFragment は使用していません。

AlertDialog 内で表示されるリストビューを外側で定義してカスタマイズするサンプルです。AlertDialog.Builder の setView で、自由にダイアログ内のメインコンテンツを差し替えれるので、ListView をそのままセットしてみました。

もちろん、ArrayAdapter を継承した独自クラスを作成して、お決まりのその中での getView の処理も実装します。問題は、表示される度に ListView をセットしなおす必要がある事をテストして知りましたが、ListView を setView でセットした後に再度 setView する場合、いったん ListView を ダイアログ内の View から削除する必要がありました。

通常、DialogFragment では無い使い方では、 AlertDialog.Builder で作成された AlertDialog は常にメモリに残っているのでそのような処理が必要なのだと思います。ボタンがクリックされる度に、AlertDialog を 毎回 1 から作成して、使うのであればそのような必要も無いのでしょうが、そうなると使用後に削除する必要が出て来るのでこのような実装になっています。

行のクリックは、ListView の OnItemClickListener で行う事になるので、AlertDialog の hide メソッドで閉じています。

また、行データの参照は、Adapter を直接参照できるので、Adapter の getItem 経由で行っています。

MainActivity 内のソースコード
private AlertDialog ad = null;
private AlertDialog.Builder ad_builder;
private MyData[] my_data;
private ListView list_view;
private MyAdapter adapter;

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

	// *********************************
	// ListView に表示するデータ
	// *********************************
	List<MyData> list = new ArrayList<MyData>();
	list.add(new MyData("大阪","27"));
	list.add(new MyData("東京","13"));
	list.add(new MyData("岡山","33"));
	list.add(new MyData("北海道","1"));
	list.add(new MyData("沖縄","47"));
	list.add(new MyData("京都","26"));
	list.add(new MyData("滋賀","25"));

	// *********************************
	// 新しく領域確保して配列データを作成する
	// *********************************
	my_data = list.toArray( new MyData[0] );

	// *********************************
	// ノーマルダイアログ作成用インスタンス作成
	// *********************************
	ad_builder = new AlertDialog.Builder(MainActivity.this);
	// *********************************
	// タイトル設定
	// *********************************
	ad_builder.setTitle("地域一覧");
	// *********************************
	// キャンセルイベント
	// *********************************
	ad_builder.setNegativeButton("キャンセル", new DialogInterface.OnClickListener() {
		@Override
		public void onClick(DialogInterface dialog, int which) {
			// キャンセル
		}
	});
	// *********************************
	// ダイアログのメインビューに
	// リストビューを設定
	// *********************************
	LayoutInflater inflater = (LayoutInflater) MainActivity.this.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
	list_view = (ListView) inflater.inflate(R.layout.listview, null);
	// *********************************
	// 行をクリックした時のイベント
	// *********************************
	list_view.setOnItemClickListener(new AdapterView.OnItemClickListener() {
		@Override
		public void onItemClick(AdapterView<?> parent, View view, int position, long id) {

			// Adapter 内の行データを取得
			MyData my_data = (MyData) adapter.getItem(position);

			// メッセージの作成
			String message = String.format("%s : %s", my_data.toString(), my_data.getValue());
			// logcat に表示
			Log.i("lightbox", message );
			// Toast で表示
			Toast.makeText(MainActivity.this,message,Toast.LENGTH_SHORT).show();
			// TextView に表示
			TextView tv;
			tv = (TextView) MainActivity.this.findViewById(R.id.textView);
			tv.setText(message);

			// 非表示
			ad.hide();
		}
	});
	// *********************************
	// カスタム・アダプター
	// *********************************
	adapter = new MyAdapter(MainActivity.this,R.layout.listview_item);
	// *********************************
	// カスタム・アダプターにデータをセット
	// *********************************
	adapter.addAll(my_data);
	// *********************************
	// ListView にカスタム・アダプターをセット
	// *********************************
	list_view.setAdapter(adapter);

	// *********************************
	// ボタンをクリック
	// *********************************
	MainActivity.this.findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {
		@Override
		public void onClick(View v) {

			// 初回はなにもしない
			if ( ad != null ) {
				// ダイアログ内の親 View から ListView を削除
				ViewGroup vg = (ViewGroup) list_view.getParent();
				vg.removeView(list_view);
			}
			// ダイアログに ListView をセット
			ad_builder.setView(list_view);
			// ダイアログの表示
			// ( AlertDialog は 非表示処理に使用 )
			ad = ad_builder.show();

		}
	});

}

// *********************************
// MyData 専用アダブター
// *********************************
private class MyAdapter extends ArrayAdapter {

	public MyAdapter(Context context, int resource) {
		super(context, resource);
	}

	@Override
	public View getView(int position, View convertView, ViewGroup parent) {

		View rowView = convertView;
		if (rowView == null) {
			// 現在の View の取得
			LayoutInflater inflater = (LayoutInflater) MainActivity.this.getSystemService
				(Context.LAYOUT_INFLATER_SERVICE);
			rowView = inflater.inflate(R.layout.listview_item, null);
		}

		MyData my_data = (MyData)this.getItem(position);

		TextView tv;
		// listview_item 内の textPlace にデータをセット
		tv = (TextView) rowView.findViewById(R.id.textPlace);
		tv.setText(my_data.toString());
		// listview_item 内の textCode にデータをセット
		tv = (TextView) rowView.findViewById(R.id.textCode);
		tv.setText(my_data.getValue());

		return rowView;
	}
}

// *********************************
// MyData
// *********************************
private class MyData {

	private String myString;
	private String myValue;

	public MyData(String myString,String myValue) {
		this.myString = myString;
		this.myValue = myValue;
	}

	// データ取得用
	String getValue() {
		return myValue;
	}

	@Override
	public String toString() {
		return myString;
	}
}


※ 全てのソースコードはこちらにあります



posted by lightbox at 2016-06-26 01:15 | Comment(0) | Android Studio 2 | このブログの読者になる | 更新情報をチェックする

AlertDialog.Builder の setItems(int itemsId, DialogInterface .OnClickListener listener) を使用した、ListView ダイアログ : ノーマルと DialogFragment の共存サンプル




ListView ダイアログの中の ListView を ArrayAdapter でカスタマイズする前のテンプレートとして作成しました。一般的には、setItems で、文字列の配列をセットするのが通常だと思われるので、どうせなら、と思ってリソースにデータを定義して使用しています。

カスタム・リストビュー・ダイアログ : ダイアログ内の ListView を ArrayAdapter でカスタムする

カスタム・リストビュー・ダイアログ : DialogFragment 内の ListView を ArrayAdapter でカスタムする

1) MainActivity 内でのリソースの参照
MainActivity.this.getResources()
2) public static class MyDialogFragment 内でのリソースの参照
MyDialogFragment.this.getActivity()

MainActivity 内のソースコード
// *********************************
// ノーマルでは ダイアログフラグメントは
// 使用していません
// *********************************
private AlertDialog.Builder ad_builder;

// *********************************
// Android 初期設定
// *********************************
@Override
protected void onCreate(Bundle savedInstanceState) {
	super.onCreate(savedInstanceState);
	// 画面の表示
	setContentView(R.layout.activity_main);

	// *********************************
	// ノーマルダイアログ作成用インスタンス
	// *********************************
	ad_builder = new AlertDialog.Builder(MainActivity.this);
	// タイトル設定
	ad_builder.setTitle("地域一覧");
	ad_builder.setNegativeButton("キャンセル", new DialogInterface.OnClickListener() {
		@Override
		public void onClick(DialogInterface dialog, int which) {
			// キャンセル
		}
	});
	// *********************************
	// ノーマル用データのセットと、行を選択時の処理
	// *********************************
	ad_builder.setItems(R.array.ken, new DialogInterface.OnClickListener() {
		@Override
		public void onClick(DialogInterface dialog, int which) {

			String[] ken = MainActivity.this.getResources().getStringArray(R.array.ken);
			int[] ken_value = MainActivity.this.getResources().getIntArray(R.array.ken_value);

			// メッセージの作成
			String message = String.format("%s : %d", ken[which], ken_value[which]);

			// logcat に表示
			Log.i("lightbox", message );

			// Toast で表示
			Toast.makeText(MainActivity.this,message,Toast.LENGTH_SHORT).show();

			// TextView に表示
			TextView tv;
			tv = (TextView) MainActivity.this.findViewById(R.id.textView);
			tv.setText(message);

		}
	});

	// *********************************
	// ボタンをクリック
	// ( ノーマル表示 )
	// *********************************
	MainActivity.this.findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {
		@Override
		public void onClick(View v) {

			// ダイアログの表示
			ad_builder.show();

		}
	});
	

	// *********************************
	// ボタンをクリック
	// ( ダイアログフラグメント で表示 )
	// *********************************
	MainActivity.this.findViewById(R.id.button2).setOnClickListener(new View.OnClickListener() {
		@Override
		public void onClick(View v) {

			// インスタンス作成
			MyDialogFragment my_dialog_fragment = new MyDialogFragment();
			// システムに引数を保存
			Bundle args = new Bundle();
			args.putString("title", "地域一覧");
			my_dialog_fragment.setArguments(args);

			// 定義しておいた newInstance メソッドを使う場合
			// MyDialogFragment my_dialog_fragment = MyDialogFragment.newInstance("地域一覧");

			// ダイアログの表示
			// ※ この環境では、getSupportFragmentManager が必要です 
			my_dialog_fragment.show(getSupportFragmentManager(),"dialog");

		}
	});

}

// *********************************
// ここから、ダイアログフラグメントを使用して
// 表示する処理用のクラス定義です
// ダイアログフラグメント( static )
// *********************************
public static class MyDialogFragment extends DialogFragment {

	// ユーザが呼び出す為のもの( 外側で処理しても良い )
	public static MyDialogFragment newInstance(String title) {
		// インスタンス作成
		MyDialogFragment my_dialog_fragment = new MyDialogFragment();
		// システムに引数を保存
		Bundle args = new Bundle();
		args.putString("title", title);
		my_dialog_fragment.setArguments(args);
		return my_dialog_fragment;
	}

	// *********************************
	// ここが、ダイアログの作成時に呼ばれます
	// 初回はユーザがダイアログを作成
	// デバイスを横にした時はシステムが
	// ダイアログを作成します
	// *********************************
	@Override
	public Dialog onCreateDialog(Bundle savedInstanceState) {

		// この中で使用するダイアログ作成用
		AlertDialog.Builder ad_builder_in_fragment;

		// この DialogFragment に保存されているデータを取得
		// ( システムが再作成した場合でも Bundle よりデータを取得できます )
		String title = MyDialogFragment.this.getArguments().getString("title");

		// DialogFragment から Activity を取得して使う
		// ( ダイアログが使われる Activity を MyDialogFragment 内で取得しています )
		ad_builder_in_fragment = new AlertDialog.Builder(MyDialogFragment.this.getActivity());
		// タイトル設定
		ad_builder_in_fragment.setTitle(title);
		ad_builder_in_fragment.setNegativeButton("キャンセル", new DialogInterface.OnClickListener() {
			@Override
			public void onClick(DialogInterface dialog, int which) {
				// キャンセル
			}
		});
		// データのセットと、行を選択時の処理
		ad_builder_in_fragment.setItems(R.array.ken, new DialogInterface.OnClickListener() {
			@Override
			public void onClick(DialogInterface dialog, int which) {

				// 現在の Activity
				// ( ダイアログが使われる Activity を MyDialogFragment 内で取得しています )
				FragmentActivity this_activity = MyDialogFragment.this.getActivity();

				// リソースより表示データと対応する内部コードを取得しています
				String[] ken = this_activity.getResources().getStringArray(R.array.ken);
				int[] ken_value = this_activity.getResources().getIntArray(R.array.ken_value);

				// メッセージの作成
				String message = String.format("%s : %d", ken[which], ken_value[which]);

				// logcat に表示
				Log.i("lightbox", message );

				// Toast で表示
				Toast.makeText(this_activity,message,Toast.LENGTH_SHORT).show();

				// TextView に表示
				TextView tv;
				tv = (TextView) this_activity.findViewById(R.id.textView);
				tv.setText(message);

			}
		});


		// ダイアログを作成してシステムに返す
		return ad_builder_in_fragment.create();
	}
}


※ 全てのソースコードはこちらにあります




posted by lightbox at 2016-06-26 00:49 | Comment(0) | Android Studio 2 | このブログの読者になる | 更新情報をチェックする

2016年06月15日


AsyncTask<Params, Progress, Result> の Progress を無しにして、onPostExecute 内の処理を interface を使って MainActivity 側で実行できるようにするクラスの作成と実行 : ジェネリクスの継承

通常使うのが、doInBackground で、その戻り値が UI スレッドで実行できればいい、と言うことを大目的としたクラスです。

UI スレッドに処理を投げる為に、専用の execute メソッドほもう一つ作って、そこの第一引数にイベントとしてインターフェイスを使うようにしています。

doInBackground は、Override される事が前提なので、なにもしていません。

StdAsyncTask
package lightbox.june.loadimage;

import android.os.AsyncTask;

public class StdAsyncTask<Params, Result> extends AsyncTask<Params, Void, Result> {

	UiAction<Result> ua = null;

	public interface UiAction<Result> {
		public void actUiAction(Result result);
	}

	public void execute(UiAction<Result> ua, Params... params) {
		this.ua = ua;
		this.execute(params);
	}

	@Override
	protected Result doInBackground(Params... params) {
		return null;
	}

	@Override
	protected void onPostExecute(Result result) {
		if( ua != null ) {
			ua.actUiAction(result);
		}
	}

}

AsyncTask の 二番目の 仮型引数 を Void にして、継承したほうでは必要無いようにしています。

そして、これをこのまま使ってもいいのですが、Drawable のデータを取得するという目的に特化した、StdAsyncTask を継承した HttpDrawable クラスが以下になります。
package lightbox.june.loadimage;

import android.graphics.drawable.Drawable;
import android.util.Log;

import java.io.InputStream;
import java.net.URL;

public class HttpDrawable extends StdAsyncTask<String,Drawable> {

	@Override
	protected Drawable doInBackground(String... params) {

		Drawable image = null;
		try {
			// インターネット上の画像を取得して、Drawable に変換
			URL url = new URL((String) params[0]);
			InputStream is = (InputStream)url.getContent();
			image = Drawable.createFromStream(is, "");
			is.close();
		}
		catch(Exception e) {
			Log.i("button", "インターネットのアクセスでエラーが発生しました");
		}
		return image;
	}

}


実行は、URL を用意して、イベント処理で画面の処理を行うのみとなります。
package lightbox.june.loadimage;

import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.ImageView;

public class MainActivity extends AppCompatActivity {

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

		MainActivity.this.findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {
			@Override
			public void onClick(View v) {

				String url = "http://news.biglobe.ne.jp/img/blnews/animal160128_02.jpg";
				HttpDrawable hd = new HttpDrawable();
				hd.execute(new StdAsyncTask.UiAction<Drawable>() {
					@Override
					public void actUiAction(Drawable drawable) {

						ImageView iv = (ImageView) MainActivity.this.findViewById(R.id.imageView);
						if ( drawable != null ) {
							iv.setImageDrawable(drawable);
						}

					}
				},url);

			}
		});
	}
}



posted by lightbox at 2016-06-15 21:53 | Comment(0) | Android Studio 2 | このブログの読者になる | 更新情報をチェックする

2016年06月06日


javamail-android + AsyncTask でメール送信を行う為のテンプレート

ソースのみのダウンロード


Gmail を使用する場合は、安全性の低いアプリの許可を『有効』にする 必要があります。

Gmail のサーバ : smtp.gmail.com
▼ 以下参考
Yahoo メールのサーバ : smtp.mail.yahoo.co.jp
Microsoft のサーバ : smtp.live.com

※ AndroidManifest.xml で、インターネットアクセスの許可が必要です。

▼ 実行画面



▼ javamail-android ライブラリ

ダウンロード
libs フォルダに 3つとも ダウンロードして、mail.jar のみ 右クリックから Add as Library で登録します

▼ AndroidSendmail クラス
package lightbox.june.mailpost;

import android.os.AsyncTask;

import java.util.Properties;

import javax.mail.Authenticator;
import javax.mail.Message;
import javax.mail.PasswordAuthentication;
import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;
import javax.mail.internet.MimeUtility;

/**
 * Created by lightbox on 2016/06/06.
 */
public class AndroidSendmail {

	// AsyncTask のインラインで参照する為の final
	private final String server;
	private final String port;
	private final String userid;
	private final String password;
	private final String username;

	// ************************************
	// コンストラクタ
	// ************************************
	public AndroidSendmail(
		String server,
		String port,
		String userid,
		String password,
		String username) {

		this.server = server;
		this.port = port;
		this.userid = userid;
		this.password = password;
		this.username = username;

//		Log.i("lightbox","コンストラクタ");
//		Log.i("lightbox",server);
//		Log.i("lightbox",port);
//		Log.i("lightbox",userid);
	}

	// ************************************
	// AsyncTask の onPostExecute から外部イベントとして
	// 呼び出す為のインターフェイス
	// ************************************
	public interface SendMailed {
		public void onMailResult(String result);
	}

	// ************************************
	// メール送信
	// ************************************
	public void SendMail(String to, String from, String subject, String body,final SendMailed sm) {

		new AsyncTask<String, Void, String>() {

			// ************************************
			// 非同期処理
			// ************************************
			@Override
			protected String doInBackground(String... params) {

//				Log.i("lightbox","開始");

				String result_string = "";
				try {
					// ************************************
					// プロパティオブジェクトを作成
					// プロパティオブジェクトは、extends Hashtable(連想配列)
					// ************************************
					Properties props = new Properties();

					// ************************************
					// * 連想配列に送信用サーバのアドレスをセット
					// ************************************
//					props.put("mail.smtp.host","smtp.mail.yahoo.co.jp");	// Yahoo
//					props.put("mail.smtp.host","smtp.live.com");	// Microsoft
					props.put("mail.smtp.host", server);
					props.put("mail.smtp.port", port);
					props.put("mail.smtp.auth", "true" );	// SMTP 認証を行う

					// ************************************
					// SSL関連設定
					// ************************************
					if ( port.equals("465") ) {
						props.put("mail.smtp.socketFactory.class", "javax.net.ssl.SSLSocketFactory");
						props.put("mail.smtp.socketFactory.fallback", "false");
						props.put("mail.smtp.socketFactory.port", port);
					}

					// ************************************
					// 暗号化
					// ************************************
					if ( port.equals("587") ) {
						props.put("mail.smtp.starttls.enable", "true");
					}


					// ************************************
					// メール用のセッションを作成
					// ************************************
					SimpleAuthenticator sa =
						new SimpleAuthenticator(userid, password);
					Session MailSession =
						Session.getInstance( props, sa );

					// ************************************
					// メール用のメッセージオブジェクトを作成
					// ************************************
					MimeMessage msg = new MimeMessage(MailSession);

					// ************************************
					// 宛先
					// ( 第一引数では、CC や BCC を指定できます。)
					// ( 第二引数は、配列も使えます / その場合は、setFrom に準じます )
					// ************************************
					msg.setRecipients(
						Message.RecipientType.TO,
						params[0]
					);

					// ************************************
					// 送信者
					// ************************************
					msg.setFrom(
						new InternetAddress( params[1], username, "ISO-2022-JP" )
					);

					// ************************************
					// 件名
					// ************************************
					msg.setSubject(
						MimeUtility.encodeText(
							params[2],
							"iso-2022-jp",
							"B"
						)
					);

					// ************************************
					// 本文
					// ************************************
					msg.setContent(
						params[3],
						"text/plain; charset=\"iso-2022-jp\""
					);

					// ************************************
					// 送信
					// ************************************
					Transport.send( msg );

					result_string = "メールの送信を完了しました";

				}
				catch( Exception e ) {
					e.printStackTrace();
					result_string = "メールの送信に失敗しました";
				}

//				Log.i("lightbox","終了");

				return result_string;
			}

			// ************************************
			// 非同期処理終了後の処理( 画面へのアクセスが可能 )
			// ************************************
			@Override
			protected void onPostExecute(String result) {
				// 引数のインターフェイス内のメソッドを呼び出す
				sm.onMailResult(result);
			}

		}.execute(to, from, subject, body);

	}

	// ************************************
	// 認証用のプライベートクラス
	// ************************************
	private class SimpleAuthenticator extends Authenticator {

		private String user_string = null;
		private String pass_string = null;

		public SimpleAuthenticator( String user_s, String pass_s ) {
			super();
			user_string = user_s;
			pass_string = pass_s;
		}

		protected PasswordAuthentication getPasswordAuthentication(){
			return new PasswordAuthentication( this.user_string, this.pass_string );
		}
	}
}

通常の Javamail と仕様は同じ(のよう)です。

ポートの 465(SSL) を使うか 587(TSL) を使うかで設定するプロパティが変わって来ます。テストは、Yahoo メールとHotmailで行いましたが、それぞれ 465 と 587 です。Gmail は両方可能だと思います。

Hotmail は、Outlook エイリアスという呼び名で名前を追加できますが、その名前を使用する事ができました( メールヘッダはオリジナルに戻ってました )

※ 『メール アカウント』受信トレイ>オプション>メール アカウント

呼び出し
package lightbox.june.mailpost;

import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import android.widget.EditText;

public class MainActivity extends AppCompatActivity {

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

		MainActivity.this.findViewById(R.id.button1).setOnClickListener(new View.OnClickListener() {
			@Override
			public void onClick(View v) {

				EditText passText = (EditText)MainActivity.this.findViewById(R.id.passText);
				String pass = passText.getText().toString();

				AndroidSendmail as = new AndroidSendmail(
					"smtp.gmail.com", // サーバ( 例 : "smtp.mail.yahoo.co.jp" or "smtp.live.com" )
					"465", // 465 または 587
					"", // アカウント
					pass, // パスワード
					"日本語ユーザ名"
				);

				// 宛先
				EditText to = (EditText)MainActivity.this.findViewById(R.id.editText2);
				String atesaki = to.getText().toString();

				// from の メールアドレス
				String myAddress = "";

				// 件名
				EditText subject = (EditText)MainActivity.this.findViewById(R.id.editText3);
				String kenmei = subject.getText().toString();

				// 本文
				EditText body = (EditText)MainActivity.this.findViewById(R.id.editText1);
				String honbun = body.getText().toString();

				as.SendMail(
					atesaki,
					myAddress,
					kenmei,
					honbun,
					new AndroidSendmail.SendMailed() {
						@Override
						public void onMailResult(String result) {
							Log.i("lightbox", result);
						}
					}
				);


			}
		});

	}
}


画面定義

エミュレータ上でパスワードを入れる事を想定して、入力時にパスが解らないように android:textColor="#efefef" となっています。

枠線は、drawable に border.xml を定義して android:background="@drawable/border" と指定しています
<?xml version="1.0" encoding="utf-8"?>
<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:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="lightbox.june.mailpost.MainActivity">

    <Button
        android:id="@+id/button1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentTop="true"
        android:layout_centerHorizontal="true"
        android:text="送信" />

    <EditText
        android:id="@+id/editText1"
        android:layout_width="match_parent"
        android:layout_height="100dp"
        android:layout_below="@+id/textView1"
        android:layout_marginTop="2dp"
        android:background="@drawable/border"
        android:ems="10"
        android:gravity="top|left"
        android:paddingBottom="10dp"
        android:paddingLeft="10dp"
        android:paddingRight="10dp"
        android:paddingTop="10dp"
        android:inputType="textMultiLine">

        <requestFocus />
    </EditText>

    <TextView
        android:id="@+id/textView1"
        android:layout_width="match_parent"
        android:layout_height="40dp"
        android:layout_below="@+id/button1"
        android:layout_marginTop="15dp"
        android:background="@drawable/border"
        android:paddingLeft="10dp"
        android:text="メール送信"
        android:textSize="30sp" />

    <EditText
        android:id="@+id/editText2"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_below="@+id/editText1"
        android:layout_marginTop="2dp"
        android:background="@drawable/border"
        android:ems="10"
        android:paddingBottom="10dp"
        android:paddingLeft="10dp"
        android:paddingRight="10dp"
        android:paddingTop="10dp"
        android:layout_alignEnd="@+id/editText1"
        android:layout_toRightOf="@+id/textView2"
        android:layout_marginLeft="18dp"
        />

    <TextView
        android:id="@+id/textView2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="宛先"
        android:textSize="20sp"
        android:layout_below="@+id/editText1"
        android:gravity="center_vertical"
        android:layout_alignBottom="@+id/editText2"/>

    <TextView
        android:id="@+id/textView3"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@+id/textView2"
        android:text="件名"
        android:textSize="20sp"
        android:layout_alignEnd="@+id/textView2"
        android:layout_marginTop="2dp"/>

    <EditText
        android:id="@+id/editText3"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_below="@+id/textView3"
        android:background="@drawable/border"
        android:ems="10"
        android:textSize="20sp"
        android:paddingBottom="10dp"
        android:paddingLeft="10dp"
        android:paddingRight="10dp"
        android:paddingTop="10dp"
        android:layout_alignEnd="@+id/editText2"/>

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="パス"
        android:id="@+id/textView"
        android:layout_below="@+id/editText3"
        android:textSize="20dp"
        android:layout_marginTop="2dp"/>

    <EditText
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:inputType="textPassword"
        android:ems="10"
        android:id="@+id/passText"
        android:background="@drawable/border"
        android:padding="10dp"
        android:layout_below="@+id/textView"
        android:textColor="#efefef"/>

</RelativeLayout>


border.xml
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
       android:shape="rectangle">

    <corners
        android:radius="4dp" />

    <stroke
        android:width="1dp"
        android:color="@android:color/black" />

</shape>

関連する記事

VS2010(C#) : TKMP.DLL(3.1.2)を使った、メール送信テンプレート
javamail 1.4.4 でメール送信




posted by lightbox at 2016-06-06 14:54 | Android Studio 2 | このブログの読者になる | 更新情報をチェックする
Seesaa の各ページの表示について
Seesaa の 記事がたまに全く表示されない場合があります。その場合は、設定> 詳細設定> ブログ設定 で 最新の情報に更新の『実行ボタン』で記事やアーカイブが最新にビルドされます。

Seesaa のページで、アーカイブとタグページは要注意です。タグページはコンテンツが全く無い状態になりますし、アーカイブページも歯抜けページはコンテンツが存在しないのにページが表示されてしまいます。

また、カテゴリページもそういう意味では完全ではありません。『カテゴリID-番号』というフォーマットで表示されるページですが、実際存在するより大きな番号でも表示されてしまいます。

※ インデックスページのみ、実際の記事数を超えたページを指定しても最後のページが表示されるようです

対処としては、このようなヘルプ的な情報を固定でページの最後に表示するようにするといいでしょう。具体的には、メインの記事コンテンツの下に『自由形式』を追加し、アーカイブとカテゴリページでのみ表示するように設定し、コンテンツを用意するといいと思います。


※ エキスパートモードで表示しています

アーカイブとカテゴリページはこのように簡単に設定できますが、タグページは HTML 設定を直接変更して、以下の『タグページでのみ表示される内容』の記述方法で設定する必要があります

<% if:page_name eq 'archive' -%>
アーカイブページでのみ表示される内容
<% /if %>

<% if:page_name eq 'category' -%>
カテゴリページでのみ表示される内容
<% /if %>

<% if:page_name eq 'tag' -%>
タグページでのみ表示される内容
<% /if %>
この記述は、以下の場所で使用します


Windows
container 終わり

フリーフォントで簡単ロゴ作成
フリーフォントでボタン素材作成
フリーフォントで吹き出し画像作成
フリーフォントではんこ画像作成
ほぼ自由に利用できるフリーフォント
フリーフォントの書体見本とサンプル
画像を大きく見る為のウインドウを開くボタンの作成

Android SDK ポケットリファレンス
改訂版 Webデザイナーのための jQuery入門
今すぐ使えるかんたん ホームページ HTML&CSS入門
CSS ドロップシャドウの参考デモ
PHP正規表現チェッカー
Google Hosted Libraries
cdnjs
BUTTONS (CSS でボタン)
イラストAC
ぱくたそ
写真素材 足成
フリーフォント一覧
utf8 文字ツール
右サイド 終わり
base 終わり