Xamarin.AndroidでViewPagerをlayout_height="wrap_content"にする(高さを変更する)

AndroidのViewPagerはlayout_height="wrap_content"

を設定してもデフォルトでは画面一杯にViewPagerが表示されてしまいます。

下記のリンクでこの問題に対する解決策は提案されていますが、

stackoverflow.com

 

Xamarin.Androidではどうしたら良いのかというのを、

Xamarinの公式ホームページにあるサンプルを元に実装していきたいと思います。

TreePager - Xamarin

 

まずは、ViewPagerの下に表示する項目を追加します。

上から順にViewPager , 画像の説明文という並びにしたいので、

ViewPagerをLinearLayoutの子要素にします。

さらにそのLinearLayoutにLinearLayoutを追加し、

子要素としてTextViewを加えます。

ついでに、ここでViewPagerをlayout_height="wrap_content"にしてみます。


<?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"
    android:textAlignment="center">

	<android.support.v4.view.ViewPager
    	android:id="@+id/viewpager"
    	android:layout_width="match_parent"
    	android:layout_height="wrap_content">
    <android.support.v4.view.PagerTabStrip
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="top"
        android:paddingBottom="10dp"
        android:paddingTop="10dp"
        android:textColor="#fff" />
	</android.support.v4.view.ViewPager>
	
	<LinearLayout
        android:orientation="vertical"
        android:minWidth="25px"
        android:minHeight="25px"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/linearLayout2">
        <TextView
            android:textAppearance="?android:attr/textAppearanceMedium"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:id="@+id/textView1"
            android:gravity="center"
            android:text="画像1の説明です"
            android:layout_marginTop="78.0dp"
            android:layout_marginBottom="61.5dp"
            android:textColor="@android:color/white"/>
    </LinearLayout>
	
</LinearLayout>

この状態で一度起動してみます。

 

f:id:dev_suesan:20170307015456p:plain

 

やはりこれでは下のTextViewは表示されないようです。

それではリンクにあるやり方を参考にして、ViewPagerを継承したサブクラスを作っていこうと思います。

今回はこのサブクラスをMyPagerとします。

MyPagerではViewPagerのOnMeasureメソッドを拡張します


using Android.Views;
using Android.Widget;

namespace TreePager
{
	public class MyPager : ViewPager
	{
		//両コンストラクタも必要になるため記述します。
		public MyPager(Context subContext) : base(context: subContext)
		{
		}

		public MyPager(Context subContext, IAttributeSet subIAttributeSet) : base(context: subContext, attrs: subIAttributeSet)
		{
		}

		//このメソッドでViewPagerの高さが決まります。
		protected override void OnMeasure(int widthMeasureSpec, int heightMeasureSpec)
		{
			//このメソッドが呼ばれるタイミングとしては、初期表示時と画像切り替わり時のようです。
			//childに設定されるのは前後のImageViewとPagerTabStripで、このサンプルの場合は、
			//1枚目の画像がやたらと大きいので1枚目と2枚目の表示がうまくいきません。
			int height = 0;
			for (int i = 0; i < this.ChildCount; i++)
			{
				View child = this.GetChildAt(i);
				if (!(child is ImageView)) continue;
				child.Measure(widthMeasureSpec, MeasureSpec.MakeMeasureSpec(0, MeasureSpecMode.Unspecified));
				int h = child.MeasuredHeight;
				if (h > height) height = h;
			}

			heightMeasureSpec = MeasureSpec.MakeMeasureSpec(height, MeasureSpecMode.Exactly);

			//ここで固定の高さを渡してあげる方法だと安定するかもです
			//heightMeasureSpec = MeasureSpec.MakeMeasureSpec(800, MeasureSpecMode.Exactly);

			base.OnMeasure(widthMeasureSpec, heightMeasureSpec);
		}
	}
}

次にレイアウトに設定されているViewPagerをMyPagerに置き換えます

※継承した要素を指定する場合はnamespace名(小文字).クラス名で指定します。


<?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"
    android:textAlignment="center">

	<treepager.MyPager
    	android:id="@+id/viewpager"
    	android:layout_width="match_parent"
    	android:layout_height="wrap_content">
    <android.support.v4.view.PagerTabStrip
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="top"
        android:paddingBottom="10dp"
        android:paddingTop="10dp"
        android:textColor="#fff" />
	</treepager.MyPager>
	
	<LinearLayout
        android:orientation="vertical"
        android:minWidth="25px"
        android:minHeight="25px"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/linearLayout2">
        <TextView
            android:textAppearance="?android:attr/textAppearanceMedium"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:id="@+id/textView1"
            android:gravity="center"
            android:text="画像1の説明です"
            android:layout_marginTop="78.0dp"
            android:layout_marginBottom="61.5dp"
            android:textColor="@android:color/white"/>
    </LinearLayout>
	
</LinearLayout>

これで実行してみると下の画像のようになります。

※固定の高さで設定しています。

 

f:id:dev_suesan:20170307015522p:plain

 

 

上手く表示されました!!

 

MyPagerのコード中にも記載しましたが、

サンプルの用に子の要素の高さだけで判断すると上手くいかないケースがあります

(もちろんリンク先の投稿者も一例として示したのだと思います)。

 

実際に使用する場合は、環境に合わせて値を算出して設定する必要があります。

 

Xamarin.FormsでSQLiteを使う

Xamarin.FormsでSQLiteを使用するには、

専用のパッケージはもちろん必要になるのですが、

Xamarin.Formsは各プラットーフォームに指示を送る司令塔の様な役割のため、

Xamarin.Forms自体がデータベースの実ファイルを持つわけではありません。

 

なので、Xamarin.FormsでSQLiteを使用するには、

各プラットフォームからデータベースへのパスを渡してあげる必要があります。

 ※ここではSqliteSampleというソリューションを作成しました。

 

Android

MainActivity.cs


using Android.App;
using Android.Content.PM;
using Android.OS;

namespace SqliteSample.Droid
{
	[Activity(Label = "SqliteSample.Droid", Icon = "@drawable/icon", Theme = "@style/MyTheme", MainLauncher = true, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation)]
	public class MainActivity : global::Xamarin.Forms.Platform.Android.FormsAppCompatActivity
	{
		protected override void OnCreate(Bundle bundle)
		{
			TabLayoutResource = Resource.Layout.Tabbar;
			ToolbarResource = Resource.Layout.Toolbar;

			base.OnCreate(bundle);

			global::Xamarin.Forms.Forms.Init(this, bundle);

			//指定したファイルのパスを取得します。
			var dbPath = GetLocalFilePath("sqlitetest.db3");

			//この段階ではまだエラーになります。
			LoadApplication(new App(dbPath));
		}

		public static string GetLocalFilePath(string filename)
		{
			//指定されたファイルのパスを取得します。なければ作成してそのパスを返却します
			var path = System.Environment.GetFolderPath(System.Environment.SpecialFolder.Personal);
			return System.IO.Path.Combine(path, filename);
		}
	}
} 
    

iOS

AppDelegate.cs


using System;

using Foundation;
using UIKit;

namespace SqliteSample.iOS
{
	[Register("AppDelegate")]
	public partial class AppDelegate : global::Xamarin.Forms.Platform.iOS.FormsApplicationDelegate
	{
		public override bool FinishedLaunching(UIApplication app, NSDictionary options)
		{
			global::Xamarin.Forms.Forms.Init();

			//指定したファイルのパスを取得します。
			var dbPath = GetLocalFilePath("culculate.db3");

			//この段階ではまだエラーになります。
			LoadApplication(new App(dbPath));

			return base.FinishedLaunching(app, options);
		}

		public static string GetLocalFilePath(string filename)
		{
			//指定されたファイルのパスを取得します。なければ作成してそのパスを返却します
			var docFolder = Environment.GetFolderPath(Environment.SpecialFolder.Personal);
			var libFolder = System.IO.Path.Combine(docFolder, "..", "Library", "Databases");

			if (!System.IO.Directory.Exists(libFolder)) {
				System.IO.Directory.CreateDirectory(libFolder);
			}

			return System.IO.Path.Combine(libFolder, filename);
		}
	}
}
    

Xamarin.Forms

App.xaml.cs


using Xamarin.Forms;

namespace SqliteSample
{
	public partial class App : Application
	{
		//データベースのパスを格納
		public static string dbPath;

		//コンストラクタの引数にstring型の引数を追加
		public App(string dbPath)
		{
			//AppのdbPathに引数のパスを設定します
			App.dbPath = dbPath;

			InitializeComponent();

			MainPage = new SqliteSamplePage();
		}

		protected override void OnStart()
		{
			// Handle when your app starts
		}

		protected override void OnSleep()
		{
			// Handle when your app sleeps
		}

		protected override void OnResume()
		{
			// Handle when your app resumes
		}
	}
}
    

※ここではAppクラスのコンストラクタの引数を変更して、データベースのパスを渡していますが、

DependencyServiceクラスを使用する方法でも問題ありません(むしろそちらの方が良いと思います)。

 

 

次にXamarin.Forms及び各プラットーフォームのプロジェクトへ

SQLite-net PCLパッケージを追加します。

現在(2017年3月5日時点)、SQLite-net PCLの最新バージョンは、

1.3.1ですが、このバージョンを追加しようとするとエラーになるため、

1.2.1を選択してください。

または、Xamarin.Formsのプロジェクトオプションを開き、

ビルド > 全般 の.Net Portable をPCL 4.5 - Profile259 に変更します。

 

f:id:dev_suesan:20170305205427p:plain

 

f:id:dev_suesan:20170306001249p:plain

 

さらに、iOSプロジェクトで使用する場合は、

iOSのプロジェクトオプションを開き、

ビルド > iOS Build のリンカーの動作をフレームワーク SDK のみをリンクするを

選択します。

 

これでSQLiteを使うための準備が整いました。

実際の使用方法の例です。

まず、今回の例ではidと名前だけを格納するテーブルUserを作ります。

UserModelというクラスを作成し、そのクラスにテーブル構造を定義し、

さらにインサート・セレクトを行うメソッドを用意します。

UserModel.cs


using System;
using System.Collections.Generic;
using SQLite;

namespace SqliteSample
{
	//テーブル名を指定
	[Table("User")]
	public class UserModel
	{
		//プライマリキー 自動採番されます
		[PrimaryKey, AutoIncrement, Column("_id")]
		//idカラム
		public int Id { get; set; }
		//名前カラム
		public string Name { get; set; }

		//Userテーブルに行追加するためのメソッドです
		public static void insertUser(string name)
		{
			//データベースに接続します
			using (SQLiteConnection db = new SQLiteConnection(App.dbPath)) {

				try {
					//データベースにUserテールブを作成します
					db.CreateTable();

					//Userテーブルに行追加します
					db.Insert(new UserModel() { Name = name });

					db.Commit();

				} catch (Exception e) {
					
					db.Rollback();
					System.Diagnostics.Debug.WriteLine(e);

				}
			}
		}

		//Userテーブルの行データを取得します
		public static List selectUser()
		{
			using (SQLiteConnection db = new SQLiteConnection(App.dbPath)) {

				try {
					//データベースに指定したSQLを発行します
					return db.Query("SELECT * FROM [User] ");

				} catch (Exception e) {

					System.Diagnostics.Debug.WriteLine(e);
					return null;
				}
			}
		}
	}
}
    

 

SqliteSamplePage.xaml.cs
using Xamarin.Forms;

namespace SqliteSample
{
	public partial class SqliteSamplePage : ContentPage
	{
		public SqliteSamplePage()
		{
			InitializeComponent();

			var layout = new StackLayout { HorizontalOptions = LayoutOptions.Center, Margin = new Thickness { Top = 100 } };

			//Userテーブルに適当なデータを追加
			UserModel.insertUser("鈴木");
			UserModel.insertUser("田中");
			UserModel.insertUser("斎藤");

			//Userテーブルの行データを取得
			var query = UserModel.selectUser();

			foreach (var user in query) {

				//Userテーブルの名前列をLabelに書き出します
				layout.Children.Add(new Label { Text = user.Name });
			}

			Content = layout;
		}
	}
}

 

これで実行すると下の画像のようにテーブルに格納したデータが表示されます

 

 

f:id:dev_suesan:20170306003202p:plain

Xamarin.Formのアプリケーションをリリースしました。

Xamarin.Formのアプリケーションをリリースしました。

MyCalculatorというアプリケーションで、

自分で作成した計算を保存・使用できる電卓です。

 

訳あって、早期でのリリースとなりましたが、

今後機能の追加、問題点の修正を随時行う予定です。

 

f:id:dev_suesan:20170305181410j:plain

f:id:dev_suesan:20170305181444j:plain

f:id:dev_suesan:20170305181603j:plain

f:id:dev_suesan:20170305181646j:plain

f:id:dev_suesan:20170305181719j:plain

f:id:dev_suesan:20170305181755j:plain

 

MyCalculator

MyCalculator

  • Shunsuke Suzuki
  • Productivity
  • Free

 

 

Xamarin.Formsでクリップボードを使う

Xamarin.Formsでクリップボードの機能を使用するには、

Xamarin.Formsにインターフェースを作成し、

各プラットフォームのプロジェクトで作成したインターフェースを実装する必要があります。

 

まずはXamarin.Formsプロジェクトにクリップボードを使用するための

インターフェース作成します。

IClipBoard.cs


using System;
namespace ClipSample
{
	public interface IClipBoard
	{
		//ペースト用メソッド
		String GetTextFromClipBoard();
		//コピー用メソッド
		bool SetTextToClipBoard(string text);
	}
}

 

Androidプロジェクトにインターフェースの実装クラスを作成します

ClipBoard_Droid.cs


using Android.Content;
using ClipSample.Droid;
using Xamarin.Forms;

[assembly: Dependency(typeof(ClipBoard_Droid))]

namespace ClipSample.Droid
{
	
	public class ClipBoard_Droid : IClipBoard
	{
		//ペースト用メソッド
		public string GetTextFromClipBoard()
		{
			//クリップボードからテキストを取得
			var clipboardmanager = (ClipboardManager)Forms.Context.GetSystemService(Context.ClipboardService);
			var item = clipboardmanager.PrimaryClip.GetItemAt(0);
			var text = item.Text;
			return text;
		}
		//コピー用メソッド
		public bool SetTextToClipBoard(string text)
		{
			//引数のテキストをクリップボードに格納
			var clipboardManager = (ClipboardManager)Forms.Context.GetSystemService(Context.ClipboardService);
			ClipData clip = ClipData.NewPlainText("", text);
			clipboardManager.PrimaryClip = clip;

			return true;
		}
	}
}

 

iOSプロジェクトにインターフェースの実装クラスを作成します

ClipBoard_iOS.cs


using Foundation;
using ClipSample.iOS;
using UIKit;

[assembly: Xamarin.Forms.Dependency(typeof(ClipBoard_iOS))]

namespace ClipSample.iOS
{
	public class ClipBoard_iOS:IClipBoard
	{
		//ペースト用メソッド
		public string GetTextFromClipBoard()
		{
			//クリップボードからテキストを取得
			var pb = UIPasteboard.General.GetValue("public.utf8-plain-text");
			return pb.ToString();
		}
		//コピー用メソッド
		public bool SetTextToClipBoard(string text){
			//引数のテキストをクリップボードに格納
			UIPasteboard.General.SetValue(new NSString(text), MobileCoreServices.UTType.Text);
			return true;
		}
	}
}

Xamarin.Formsプロジェクトで実際に使用するには、

DepedencyServiceクラスを使用して、各プラットフォームで作成した

実装クラスのメソッドを呼び出します。

ClipSamplePage.xaml.cs


namespace ClipSample
{
	public partial class ClipSamplePage : ContentPage
	{
		//中略
		
		//ペースト
		public void onPast(object sender, EventArgs e)
		{
			var clipboardText = DependencyService.Get().GetTextFromClipBoard();
		}
		
		//コピー
		public void onCopy(object sender, EventArgs e)
		{
			DependencyService.Get().SetTextToClipBoard("コピーするテキスト");
		}
	}
}