Quantcast
Channel: かずきのBlog@hatena
Viewing all 1388 articles
Browse latest View live

project_name:wear:unspecifiedという謎エラー

$
0
0

Android StudioでAndroid Wearのプロジェクト作ってビルドするとこんなエラーが出た。

Error:A problem occurred configuring project ':mobile'.
> Could not resolve all dependencies for configuration ':mobile:_debugCompile'.
   > A problem occurred configuring project ':wear'.
      > Could not resolve all dependencies for configuration ':wear:_debugCompile'.
         > Could not find any version that matches com.google.android.support:wearable:+.
           Required by:
               MyApplication4:wear:unspecified
         > Could not find any version that matches com.google.android.gms:play-services-wearable:+.
           Required by:
               MyApplication4:wear:unspecified

以下のページに解決策がありました。

うちでは、Google Repositoriesというものが入ってなかったのでインストール。無事動くようになりました。


Xamarin.AndroidでSQLiteを使う

$
0
0

XamarinというかAndroidでSQLiteを使う時は、SQLiteOpenHelperというクラスを継承して使います。

こいつのコンストラクタは、コンテキスト、データベース名、カーソルファクトリ、データベースのバージョンという4つの引数を渡すのが一般的です。カーソルファクトリはnullでも良さそうなのですが、今回はカーソルファクトリまで実装してみました。

順を追って説明します。

Cursorの実装

今回は以下のようなテーブルを前提にしています。

createtable People (
    id integer primary key autoincrement,
    name varchar(150) notnull
);

このテーブルのデータをタイプセーフに取得するためのカーソルを実装します。カーソルは、SQLiteCursorクラスを継承する形で実装します。コンストラクタは親クラスのを呼び出す形で実装して、テーブルの列に対応するプロパティを定義します。

プロパティ内は、GetColumnIndexメソッドで列名から列のインデックスを取得して、インデックスからGet型名のメソッドを使って値を取得しています。

// カーソルクラスclass PeopleCursor : SQLiteCursor
{
    public PeopleCursor(ISQLiteCursorDriver driver, string editTable, SQLiteQuery query)
        : base(driver, editTable, query)
    {
    }

    // タイプセーフに列のデータを取るためのプロパティを定義しておくpubliclong Id { get { returnthis.GetLong(this.GetColumnIndex("id")); } }
    publicstring Name { get { returnthis.GetString(this.GetColumnIndex("name")); } }
}

CursorFactoryの実装

次に、CursorFactoryの実装です。こいつは、SQLiteDatabase.ICursorFactoryインターフェースを実装します。実装時のポイントとしてはJava.Lang.Objectを継承しておく点です。これがないとうまく動きません。

NewCursorメソッドで、先ほど作成したPeopleCursorを作成して返します。

// カーソルファクトリ。Java.Lang.Objectを継承するのがポイントsealedclass PeopleCursorFactory : Java.Lang.Object, SQLiteDatabase.ICursorFactory
{
    public Android.Database.ICursor NewCursor(SQLiteDatabase db, ISQLiteCursorDriver masterQuery, string editTable, SQLiteQuery query)
    {
        // 自前のカーソルを作って返すreturnnew PeopleCursor(masterQuery, editTable, query);
    }

}

SQLiteOpenHelper

最後に、SQLiteOpenHelperを継承したクラスを作成します。こいつは、コンストラクタで、コンテキスト、データベース名、カーソルファクトリ(nullでもOK)、データベースのバージョンを渡します。

そしてOnCreateメソッドでデータベース作成時の処理(create tableなどを実行する)をして、OnUpgradeメソッドでデータベースのバージョンが上がった時の処理(create tableやalter tableなどを実行する)を行います。

さくっと実装すると以下のような感じになります。

class DatabaseHelper : SQLiteOpenHelper
{
    privateconststring DbName = "sample.db";
    privateconststring Table = "People";
    privateconstint DbVersion = 1;
    privateconststring CreateTable = @"        create table People (            id integer primary key autoincrement,            name varchar(150) not null        );";

    public DatabaseHelper(Context context) : base(context, DbName, new PeopleCursorFactory(), DbVersion)
    {
    }
    
    publicoverridevoid OnCreate(SQLiteDatabase db)
    {
        // テーブルを作って
        db.ExecSQL(CreateTable);

        // テストデータを入れる
        var values = new ContentValues();
        values.Put("name", "tanaka");
        db.Insert(Table, null, values);
    }

    publicoverridevoid OnUpgrade(SQLiteDatabase db, int oldVersion, int newVersion)
    {
    }

}

Insert, Update, Delete, QueryメソッドがSQLiteDatabaseクラスに定義されているので、これを使ってDBに問い合わせてなんやかんやします。

使ってみる

ActivityでDBに問い合わせを行い、最初の行のデータをTextViewに表示するコード例です。

[Activity(Label = "App10", MainLauncher = true, Icon = "@drawable/icon")]
publicclass MainActivity : Activity
{
    privatestring message;

    protectedoverridevoid OnCreate(Bundle bundle)
    {
        base.OnCreate(bundle);
        SetContentView(Resource.Layout.Main);

        var db = new DatabaseHelper(this);
        var cursor = (PeopleCursor) db.ReadableDatabase.Query("People", new[] { "id", "name" }, null, null, null, null, "id");
        cursor.MoveToFirst();
        this.message = string.Format("{0} {1}", cursor.Id, cursor.Name);

        this.FindViewById<TextView>(Resource.Id.Main_Label).Text = this.message;
    }
}

画面にMain_LabelというIDのTextViewがあることを前提としています。先ほど作ったDatabaseHelperクラスのインスタンスを作って、ReadableDatabaseのQueryメソッドでデータを取得しています。テーブル名と取得する列名とソート条件を指定しています。nullのところはgroup byとかhavingとかを指定する箇所です。パラメータ名を見れば使い方がわかると思います。

とってきたカーソルはMoveToFirstを呼び出して最初に移動してデータを取得しています。本番では、trueを返すかどうかチェックしたほうがいいと思います。あと、ループで回してListViewに表示するのが本来の使い方だと思いますが今回はサンプルなので一行目だけデータをとってTextViewに設定してます。

まとめ

ICursorFactoryを実装するときにJava.Lang.Objectを継承するというのに気づくまでにすごい時間かかった…。

Xamarin.AndroidでContentProviderを実装する

$
0
0

SQLiteの使い方がわかったら次はContentProviderですよね。 ということで、SQLの部分はさくっと実装。

using Android.Content;
using Android.Database.Sqlite;

namespace ContentProviderSample
{
    publicclass PeopleDatabase : SQLiteOpenHelper
    {
        privateconststring DbName = "people.db";
        privateconststring TableName = "People";
        privateconststring CreateTable = @"            create table People (                _id integer primary key autoincrement,                name varchar(150)            );";
        privateconstint DatabaseVersion = 2;

        public PeopleDatabase(Context context) : base(context, DbName, new PeopleCursorFactory(), DatabaseVersion)
        {
        }
        
        publicoverridevoid OnCreate(SQLiteDatabase db)
        {
            db.ExecSQL(CreateTable);
            for (int i = 0; i < 100; i++)
            {
                var c = new ContentValues();
                c.Put("name", "tanaka" + i);
                db.Insert(TableName, null, c);
            }
        }

        publicoverridevoid OnUpgrade(SQLiteDatabase db, int oldVersion, int newVersion)
        {
        }

        public PeopleCursor GetAll()
        {
            return (PeopleCursor) this.ReadableDatabase.Query(TableName, new[] { "_id", "name" }, null, null, null, null, "_id");
        }

        public PeopleCursor GetOne(long id)
        {
            return (PeopleCursor) this.ReadableDatabase.Query(TableName, new[] { "_id", "name" }, "_id = ?", new[] { id.ToString() }, null, null, null);
        }
    }

    publicclass PeopleCursor : SQLiteCursor
    {
        public PeopleCursor(ISQLiteCursorDriver driver, string editTable, SQLiteQuery query) : base(driver, editTable, query)
        {
        }

        publiclong Id
        {
            get { returnthis.GetLong(this.GetColumnIndex("_id")); }
        }

        publicstring Name
        {
            get { returnthis.GetString(this.GetColumnIndex("name")); }
        }
    }

    publicclass PeopleCursorFactory : Java.Lang.Object, SQLiteDatabase.ICursorFactory
    {
        public Android.Database.ICursor NewCursor(SQLiteDatabase db, ISQLiteCursorDriver masterQuery, string editTable, SQLiteQuery query)
        {
            returnnew PeopleCursor(masterQuery, editTable, query);
        }
    }

}

ContentProvider

コンテンツプロバイダーを作っておくと、いろいろ便利らしいので、作っておきます。

using Android.Content;
using System;

namespace ContentProviderSample
{
    // Authorityを属性で設定する
    [ContentProvider(new[] { Authority })]
    publicclass PeopleContentProvider : ContentProvider
    {
        // URI組立に必要な人たちprivateconststring Authority = "com.example.PeopleProvider";
        privateconststring BasePath = "people";
        publicstaticreadonly Android.Net.Uri ContentUri = Android.Net.Uri.Parse("content://" + Authority + "/" + BasePath);

        // Mimeprivateconststring MimeType = "/vnd.com.example.People";

        // UriMatcherのマッチ結果privateconstint GetAll = 0;
        privateconstint GetOne = 1;

        privatestaticreadonly UriMatcher UriMatcher = CreateUriMatcher();

        // UriMatcherを生成するprivatestatic UriMatcher CreateUriMatcher()
        {
            var r = new UriMatcher(UriMatcher.NoMatch);
            r.AddURI(Authority, BasePath, GetAll);
            r.AddURI(Authority, BasePath + "/#", GetOne);
            return r;
        }

        private PeopleDatabase db;

        // 列名の定数publicstaticclass PeopleColumns
        {
            publicconststring Id = "_id";
            publicconststring Name = "name";
        }

        // 削除。とりあえず未実装publicoverrideint Delete(Android.Net.Uri uri, string selection, string[] selectionArgs)
        {
            thrownew NotImplementedException();
        }

        // Uriに応じてMimeを返す。今回は同じもの返すpublicoverridestring GetType(Android.Net.Uri uri)
        {
            switch (UriMatcher.Match(uri))
            {
                case GetAll:
                case GetOne:
                    return MimeType;
                default:
                    thrownew Java.Lang.IllegalArgumentException();
            }
        }

        // 挿入。とりあえず未実装publicoverride Android.Net.Uri Insert(Android.Net.Uri uri, ContentValues values)
        {
            thrownew NotImplementedException();
        }

        // 作成時の処理。とりあえずDB作るpublicoverridebool OnCreate()
        {
            this.db = new PeopleDatabase(this.Context);
            returntrue;
        }

        // Uriを判別して全件返すか一件返すか処理をわけるpublicoverride Android.Database.ICursor Query(Android.Net.Uri uri, string[] projection, string selection, string[] selectionArgs, string sortOrder)
        {
            switch (UriMatcher.Match(uri))
            {
                case GetAll:
                    returnthis.db.GetAll();
                case GetOne:
                    returnthis.db.GetOne(long.Parse(uri.LastPathSegment));
                default:
                    thrownew Java.Lang.IllegalArgumentException();
            }
        }

        // 更新。とりあえず未実装publicoverrideint Update(Android.Net.Uri uri, ContentValues values, string selection, string[] selectionArgs)
        {
            thrownew NotImplementedException();
        }
    }
}

なんか定型でめんどくさい感じがしますね。

使う

ListViewにとりあえず表示してみようと思います。

using Android.App;
using Android.Content;
using Android.Database;
using Android.OS;
using Android.Widget;

namespace ContentProviderSample
{
    [Activity(Label = "ContentProviderSample", MainLauncher = true, Icon = "@drawable/icon")]
    publicclass MainActivity : Activity
    {
        protectedoverridevoid OnCreate(Bundle bundle)
        {
            base.OnCreate(bundle);

            // Set our view from the "main" layout resourcethis.SetContentView(Resource.Layout.Main);

            // ListViewを取得して
            var listView = this.FindViewById<ListView>(Resource.Id.Main_ListViewPeople);

            // 取得する列名を指定してstring[] projection = new[] { PeopleContentProvider.PeopleColumns.Id, PeopleContentProvider.PeopleColumns.Name };
            // 表示列を指定string[] fromColumns = new[] { PeopleContentProvider.PeopleColumns.Name };
            // 表示先コントロールのIDを指定int[] toControlIds = new[] { Android.Resource.Id.Text1 };

            // コンテンツプロバイダーからデータを読み込む
            var loader = new CursorLoader(this, PeopleContentProvider.ContentUri, projection, null, null, null);
            var cursor = loader.LoadInBackground() as ICursor;

            // カーソルを使うアダプタを作って、ListViewに設定
            var adapter = new SimpleCursorAdapter(this, Android.Resource.Layout.SimpleListItem1, cursor, fromColumns, toControlIds);
            listView.Adapter = adapter;
        }
    }
}

見事表示できた。

f:id:okazuki:20140727233014p:plain

GridViewPager + CardFragmentを使ってAndroid WearらしいUIを作る

$
0
0

Android Wearを触る機会を得たので少しアプリの作り方でも…。以下のページが超参考になります。

Androidも、Androidで動くJavaのような言語も初心者ですが、ちょっとずつね。

Android Studioでプロジェクトを作る

Android Wearのプロジェクトを作ると、四角と丸のレイアウトができるのでざくっと決して、activity_my.xmlでGridViewPagerを使うように書き換えます。

<?xml version="1.0" encoding="utf-8"?><android.support.wearable.view.GridViewPagerxmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"tools:context=".MyActivity"android:id="@+id/gridViewPager"tools:deviceIds="wear"></android.support.wearable.view.GridViewPager>

そして、MyActivityで、GridViewPagerを取得して、FragmentGridViewPagerを継承して作ったAdapterを渡してやります。FragmentGridViewPagerは、とりあえずCardFragmentを返すようにしています。CardFragmentはcreateメソッドで簡単にカードっぽいUIを作れるので素敵です。

package com.example.kazuki.myapplication;

import android.app.Activity;
import android.app.Fragment;
import android.app.FragmentManager;
import android.content.Context;
import android.hardware.SensorManager;
import android.os.Bundle;
import android.support.wearable.view.CardFragment;
import android.support.wearable.view.FragmentGridPagerAdapter;
import android.support.wearable.view.GridViewPager;
import android.widget.Button;
import android.widget.TextView;

publicclass MyActivity extends Activity {

    private TextView mTextView;
    private Button button;

    @Overrideprotectedvoid onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_my);
        final GridViewPager gv = (GridViewPager) this.findViewById(R.id.gridViewPager);
        gv.setAdapter(new MyAdapter(this.getFragmentManager()));
    }

    class MyAdapter extends FragmentGridPagerAdapter {

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

        @Overridepublic Fragment getFragment(int row, int col) {
            return CardFragment.create("sample", "sample description row = " + row + " ,column = " + col);
        }

        @Overridepublicint getRowCount() {
            return3;
        }

        @Overridepublicint getColumnCount(int i) {
            return4;
        }
    }
}

3行4列のカードが出るアプリの完成です。実行結果は以下のような感じ。

f:id:okazuki:20140728202304p:plain

まだまだ、実用アプリを作るには程遠いけど、ちょっとずつね…。

Xamarin.Androidで通知を出す

$
0
0

Notificationを出す最小限のコードは以下の通り。BlankAppを作った時のOnCreateあたりの処理を以下のコードに変えたら動きます。

protectedoverridevoid OnCreate(Bundle bundle)
{
    base.OnCreate(bundle);

    // Set our view from the "main" layout resource
    SetContentView(Resource.Layout.Main);

    // Get our button from the layout resource,// and attach an event to it
    Button button = FindViewById<Button>(Resource.Id.MyButton);

    button.Click += (_, __) =>
    {
        var n = new Notification.Builder(this)
            .SetContentTitle("Hello notification")
            .SetSmallIcon(Resource.Drawable.Icon)
            .Build();

        var nm = (NotificationManager)this.GetSystemService(Context.NotificationService);
        nm.Notify(0, n);
    };
}

これで、押しても何も起きない通知が表示されます。

これだけじゃさみしいので押したときにアプリを起動するのをやってみようと思います。NotificationにIntentを渡すことで実現します。Intentの渡し方は、TaskStackBuilderを作って、AddNextIntentメソッドを使ってIntentを設定したあとにGetPendingIntentメソッドを使ってPendingIntentを取得します。取得したPendingIntentをNotification.BuilderのSetContentIntentに設定することで、通知をクリックしたときにIntentが起動されるという感じみたいです。

protectedoverridevoid OnCreate(Bundle bundle)
{
    base.OnCreate(bundle);

    // Set our view from the "main" layout resource
    SetContentView(Resource.Layout.Main);

    // Get our button from the layout resource,// and attach an event to it
    Button button = FindViewById<Button>(Resource.Id.MyButton);

    button.Click += (_, __) =>
    {
        var pendingIntent = TaskStackBuilder.Create(this)
            .AddNextIntent(new Intent(this, typeof(MainActivity)))
            .GetPendingIntent(0, PendingIntentFlags.UpdateCurrent);

        var n = new Notification.Builder(this)
            .SetContentTitle("Hello notification")
            .SetSmallIcon(Resource.Drawable.Icon)
            .SetContentIntent(pendingIntent)
            .Build();

        var nm = (NotificationManager)this.GetSystemService(Context.NotificationService);
        nm.Notify(0, n);
    };
}

Android Wear → Androidの通信

$
0
0

こちらのページを参考に、メッセージ送信の部分だけを抜き出して作ってみました。

Android, Android Wear共通

両方のAndroidManifest.xmlのapplicationタグの下に以下の一行を追加します。こいつは通信につかうGoogleApiClientを使うのにいるっぽいです。

<meta-dataandroid:name="com.google.android.gms.version"android:value="@integer/google_play_services_version" />

Android Wear メッセージ送信側

まず、画面でGoogleApiClientのインスタンスを作っておきます。Builderがあるのでさくっと作れます。接続の成否を見るために、addConnectionCallbacksとaddOnConnectionFailedのリスナーを追加しておくといいと思われます。そして、addApiでWearable.APIを追加しておきます。

// フィールドに定義しておくprivate GoogleApiClient client;

// OnCreateとかそれに準ずるメソッドあたりで初期化this.client = new GoogleApiClient.Builder(this.getActivity())
        .addConnectionCallbacks(new GoogleApiClient.ConnectionCallbacks() {
            @Overridepublicvoid onConnected(Bundle bundle) {
                Log.d("MyFragment", "onConnected");
            }
            @Overridepublicvoid onConnectionSuspended(int i) {
                Log.d("MyFragment", "onConnectionSuspended");
            }
        })
        .addOnConnectionFailedListener(new GoogleApiClient.OnConnectionFailedListener() {
            @Overridepublicvoid onConnectionFailed(ConnectionResult connectionResult) {
                Log.d("MyFragment", "onConnectionFailed");
            }
        })
        .addApi(Wearable.API)
        .build();
this.client.connect();

次にメッセージ送信処理です。これはボタンのクリックなど、送信したいタイミングのところに書くといいと思います。ポイントはUIスレッドでawait呼ぶと死ぬので別スレッドを立ててるところです。ただ、行儀がいいのかはちょっと個人的に疑問に残ったりするところです…。

Wearable.NodeApi.GetConnectionNodesメソッドで接続のノードを取得して、全ノードに対してsendMessageメソッドでメッセージを送っています。sendMessageは、GoogleClientApiと、ノードのIDと、メッセージ識別用のパスの文字列と、バイト列です。ここではHello worldをバイト配列にして渡してます。

結果に対してisSuccessで成否の判断ができます。到達確認までできてるのかは、未確認…。

new Thread(new Runnable() {
    @Overridepublicvoid run() {
        Log.d("MyFragment", "onClick");
        final String message = "Hello world";
        NodeApi.GetConnectedNodesResult nodes = Wearable.NodeApi.getConnectedNodes(client).await();
        for (Node node : nodes.getNodes()) {
            MessageApi.SendMessageResult result = Wearable.MessageApi.sendMessage(
                    client,
                    node.getId(),
                    "/hello",
                    message.getBytes())
                    .await();
            if (result.getStatus().isSuccess()) {
                Log.d("onClick", "isSuccess is true");
            } else {
                Log.d("onClick", "isSuccess is false");
            }
        }
    }
}).start();

Android wear側のコードはこんな感じになってます。

package com.example.kazuki.myapplication;

import android.app.Activity;
import android.app.Fragment;
import android.app.FragmentManager;
import android.os.Bundle;
import android.support.wearable.view.CardFragment;
import android.support.wearable.view.FragmentGridPagerAdapter;
import android.support.wearable.view.GridViewPager;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.TextView;

import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.wearable.MessageApi;
import com.google.android.gms.wearable.Node;
import com.google.android.gms.wearable.NodeApi;
import com.google.android.gms.wearable.Wearable;

publicclass MyActivity extends Activity {

    private TextView mTextView;

    @Overrideprotectedvoid onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_my);
        final GridViewPager gvp = (GridViewPager) findViewById(R.id.gridPageView);
        gvp.setAdapter(new GridViewPagerAdapter(this.getFragmentManager()));
    }

    staticclass GridViewPagerAdapter extends FragmentGridPagerAdapter {

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

        @Overridepublic Fragment getFragment(int row, int col) {
            return MyFragment.newInstance();
        }

        @Overridepublicint getRowCount() {
            return1;
        }

        @Overridepublicint getColumnCount(int i) {
            return1;
        }
    }

    staticclass MyFragment extends CardFragment {
        private GoogleApiClient client;

        @Overridepublic View onCreateContentView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
            this.client = new GoogleApiClient.Builder(this.getActivity())
                    .addConnectionCallbacks(new GoogleApiClient.ConnectionCallbacks() {
                        @Overridepublicvoid onConnected(Bundle bundle) {
                            Log.d("MyFragment", "onConnected");
                        }

                        @Overridepublicvoid onConnectionSuspended(int i) {
                            Log.d("MyFragment", "onConnectionSuspended");
                        }
                    })
                    .addOnConnectionFailedListener(new GoogleApiClient.OnConnectionFailedListener() {
                        @Overridepublicvoid onConnectionFailed(ConnectionResult connectionResult) {
                            Log.d("MyFragment", "onConnectionFailed");
                        }
                    })
                    .addApi(Wearable.API)
                    .build();
            this.client.connect();

            Button button = new Button(this.getActivity());
            button.setText("OK");
            button.setOnClickListener(new View.OnClickListener() {
                @Overridepublicvoid onClick(View v) {
                    new Thread(new Runnable() {
                        @Overridepublicvoid run() {
                            Log.d("MyFragment", "onClick");
                            final String message = "Hello world";
                            NodeApi.GetConnectedNodesResult nodes = Wearable.NodeApi.getConnectedNodes(client).await();
                            for (Node node : nodes.getNodes()) {
                                MessageApi.SendMessageResult result = Wearable.MessageApi.sendMessage(
                                        client,
                                        node.getId(),
                                        "/hello",
                                        message.getBytes())
                                        .await();
                                if (result.getStatus().isSuccess()) {
                                    Log.d("onClick", "isSuccess is true");
                                } else {
                                    Log.d("onClick", "isSuccess is false");
                                }
                            }
                        }
                    }).start();
                }
            });
            return button;
        }

        publicstatic MyFragment newInstance() {
            returnnew MyFragment();
        }
    }
}

ついでにレイアウトファイル

<?xml version="1.0" encoding="utf-8"?><android.support.wearable.view.GridViewPagerxmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"xmlns:tools="http://schemas.android.com/tools"android:id="@+id/gridPageView"android:layout_width="match_parent"android:layout_height="match_parent"tools:context=".MyActivity"tools:deviceIds="wear"></android.support.wearable.view.GridViewPager>

受信側のAndroidの処理

受信側も、何処かでGoogleApiClientのインスタンスを作っておきます。この例では、ActivityのOnCreateで作ってます。手抜きして各種リスナーは登録しないでやりました。すっきり!

package com.example.kazuki.myapplication;

import android.app.Activity;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;

import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.wearable.Wearable;


publicclass MyActivity extends Activity  {
    private GoogleApiClient client;

    @Overrideprotectedvoid onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_my);
        this.client = new GoogleApiClient.Builder(this)
                .addApi(Wearable.API)
                .build();
        this.client.connect();
    }


    @Overridepublicboolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.my, menu);
        returntrue;
    }

    @Overridepublicboolean onOptionsItemSelected(MenuItem item) {
        // Handle action bar item clicks here. The action bar will// automatically handle clicks on the Home/Up button, so long// as you specify a parent activity in AndroidManifest.xml.int id = item.getItemId();
        if (id == R.id.action_settings) {
            returntrue;
        }
        returnsuper.onOptionsItemSelected(item);
    }
}

データの受信にはWearableListenerServiceを継承したサービスを作ります。こいつは、onMessageReceivedをオーバーライドすればOKです。メッセージがきたら呼ばれます。getPathとgetDataでsendMessageしたときのパスとデータがとれます。ここでは、受信結果をログに出しています。

package com.example.kazuki.myapplication;

import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.util.Log;

import com.google.android.gms.wearable.MessageEvent;
import com.google.android.gms.wearable.WearableListenerService;

publicclass MyService extends WearableListenerService {
    @Overridepublicvoid onMessageReceived(MessageEvent messageEvent) {
        Log.d("MyService", "onMessageReceived");
        Log.d("MyService", messageEvent.getPath());
        Log.d("MyService", new String(messageEvent.getData()));
    }
}

AndroidManifest.xmlには、以下のようなサービスの定義をしておきます。intent-filterのactionにcom.google.android.gms.wearable.BIND_LISTENERを追加しておくのがポイントです。

<serviceandroid:name=".MyService"><intent-filter><action android:name="com.google.android.gms.wearable.BIND_LISTENER" /></intent-filter></service>

これで、接続されているAndroidとAndroid Wearの両方でアプリを起動して、Android Wearでボタンを押すとAndroid側のログにメッセージが出ます。

おまけ

Android実機とAndroid Wearのエミュレータの接続方法は以下のページが参考になります。

Android Wear Previewの取得とかは今はいらないので後半の接続方法のところを参考にしてやればOKです。

ActivityからLayoutInflaterを作る

$
0
0

レイアウトファイルからViewを作ってくれるLayoutInflaterの作り方です。

LayoutInflater li = LayoutInflater.from(context);

コンストラクタでActivityでも受け取っておいてActivityから作るのが一般的かな?作ったらあとは使えばOK

View v = li.inflate(R.layout.notif_preset_list_item, null)

良く忘れるのでメモメモ。

Android WearのWearableListViewの最小の使い方

$
0
0

ぐぐっても、あんまりWearableListViewの使い方が出てこなくて難儀しました。NotificationsのサンプルにWearableListViewの使い方はありましたが、色々余計な処理が入ってたので、備忘録として、最小限の使い方をメモっておきます。

プロジェクトの作成とWearableListViewの追加

Android StudioでAndroid Wearのプロジェクトを作成して、activity_my.xmlをWearableListViewがあるだけの状態にします。

<?xml version="1.0" encoding="utf-8"?><android.support.wearable.view.WearableListViewxmlns:android="http://schemas.android.com/apk/res/android"android:id="@+id/list_view"android:layout_width="fill_parent"android:layout_height="fill_parent"></android.support.wearable.view.WearableListView>

Adapterの作成

WearableListViewもListViewと同様にAdapterを使って要素の管理をします。ふつうのとちょっと使い方が違って、WearableListView.Adapterを継承して作ります。今回はMyActivityの内部クラスとして実装しました。

class Adapter extends WearableListView.Adapter {
    private Context context;
    private LayoutInflater inflater;
    Adapter(Context context) {
        this.context = context;
        this.inflater = LayoutInflater.from(this.context);
    }
    @Overridepublic WearableListView.ViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) {
        returnnew WearableListView.ViewHolder(
                this.inflater.inflate(R.layout.list_view_item, viewGroup, false)
        );
    }
    @Overridepublicvoid onBindViewHolder(WearableListView.ViewHolder viewHolder, int i) {
        Log.d("Adapter", "index: " + i);
        TextView tv = (TextView) viewHolder.itemView.findViewById(R.id.textView);
        tv.setText("index: " + i);
    }
    @Overridepublicint getItemCount() {
        return20;
    }
}

getItemCountで要素数を返し、onCreateViewHolderで表示する要素のViewを作成します。そしてonBindViewHolderでViewとデータを結びつけます。layoutファイルから、Viewを作る場合は、上記コードのようにContextからLayoutInflaterを作っておきましょう。因みにlist_view_item.xmlは以下のようになってます。

<?xml version="1.0" encoding="utf-8"?><FrameLayoutxmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"><TextViewandroid:layout_width="fill_parent"android:layout_height="fill_parent"android:text="New Text"android:id="@+id/textView"/></FrameLayout>

ClickListenerの実装

これ地味に必須です。これをしないと、WearableListViewの上のほうの余白タップしたらアプリが落ちてました。とりあえず、WearableListView.ClickListenerを実装したクラスを用意します。ここでは、Activityに直接実装しました。onClickが現在

publicclass MyActivity extends Activity implements WearableListView.ClickListener {

    // OnCreateの定義@Overridepublicvoid onClick(WearableListView.ViewHolder viewHolder) {
        TextView v = (TextView) viewHolder.itemView.findViewById(R.id.textView);
        Log.d("MyActivity", "onClick " + v.getText());
    }

    @Overridepublicvoid onTopEmptyRegionClick() {
        Log.d("MyActivity", "onTopEmptyRegionClick");
    }

    // Adapterの定義
}

最後に、ActivityのOnCreateでClickListnerとAdapterの設定をします。

publicclass MyActivity extends Activity implements WearableListView.ClickListener {

    @Overrideprotectedvoid onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        this.setContentView(R.layout.activity_my);
        WearableListView listView = (WearableListView) this.findViewById(R.id.list_view);
        listView.setAdapter(new Adapter(this));
        listView.setClickListener(this);
    }

     // その他の定義
}

コード全体

コード全体を載せておきます。

MyActivity.java

publicclass MyActivity extends Activity implements WearableListView.ClickListener {

    @Overrideprotectedvoid onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        this.setContentView(R.layout.activity_my);
        WearableListView listView = (WearableListView) this.findViewById(R.id.list_view);
        listView.setAdapter(new Adapter(this));
        listView.setClickListener(this);
    }



    @Overridepublicvoid onClick(WearableListView.ViewHolder viewHolder) {
        TextView v = (TextView) viewHolder.itemView.findViewById(R.id.textView);
        Log.d("MyActivity", "onClick " + v.getText());
    }

    @Overridepublicvoid onTopEmptyRegionClick() {
        Log.d("MyActivity", "onTopEmptyRegionClick");
    }

    class Adapter extends WearableListView.Adapter {

        private Context context;
        private LayoutInflater inflater;

        Adapter(Context context) {
            this.context = context;
            this.inflater = LayoutInflater.from(this.context);
        }

        @Overridepublic WearableListView.ViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) {
            returnnew WearableListView.ViewHolder(
                    this.inflater.inflate(R.layout.list_view_item, viewGroup, false)
            );
        }

        @Overridepublicvoid onBindViewHolder(WearableListView.ViewHolder viewHolder, int i) {
            Log.d("Adapter", "index: " + i);
            TextView tv = (TextView) viewHolder.itemView.findViewById(R.id.textView);
            tv.setText("index: " + i);
        }

        @Overridepublicint getItemCount() {
            return20;
        }
    }
}

activity_my.xml

<?xml version="1.0" encoding="utf-8"?><android.support.wearable.view.WearableListViewxmlns:android="http://schemas.android.com/apk/res/android"android:id="@+id/list_view"android:layout_width="fill_parent"android:layout_height="fill_parent"></android.support.wearable.view.WearableListView>

list_view_item.xml

<?xml version="1.0" encoding="utf-8"?><FrameLayoutxmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"><TextViewandroid:layout_width="fill_parent"android:layout_height="fill_parent"android:text="New Text"android:id="@+id/textView"/></FrameLayout>

Android WearのWearableListViewで、中央の項目以外を選択出来ないように見せる

$
0
0

めんどくさかった…!

以下の記事のソースをもとに作成します。

WearableListView.Itemを実装したクラスを作成

色々あるけど、onScaleUpStartで中央にあるときの表示を制御して、onScaleDownStartで中央以外にあるときの表示を制御するっぽいです。そして、WearableListView.Itemを実装したクラスは、WearableListViewの要素になるので、前回FrameLayoutをlist_view_item.xmlにおいていたのと差し替えます。とりあえずさくっとFrameLayoutを継承しつつWearableListView.Itemを実装!

package com.example.kazuki.myapplication;

import android.content.Context;
import android.support.wearable.view.WearableListView;
import android.util.AttributeSet;
import android.widget.FrameLayout;
import android.widget.TextView;

/** * Created by Kazuki on 2014/08/01. */publicclass ListViewItem extends FrameLayout implements WearableListView.Item {
    private TextView textView;

    public ListViewItem(Context context) {
        super(context);
    }

    public ListViewItem(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public ListViewItem(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

    @Overrideprotectedvoid onFinishInflate() {
        super.onFinishInflate();
        this.textView = (TextView) this.findViewById(R.id.textView);
    }

    @Overridepublicfloat getProximityMinValue() {
        return0f;
    }

    @Overridepublicfloat getProximityMaxValue() {
        return0f;
    }

    @Overridepublicfloat getCurrentProximityValue() {
        return0f;
    }

    @Overridepublicvoid setScalingAnimatorValue(float scale) {
    }

    @Overridepublicvoid onScaleUpStart() {
        this.textView.setAlpha(1f);
    }

    @Overridepublicvoid onScaleDownStart() {
        this.textView.setAlpha(0.5f);
    }
}

list_view_item.xmlの変更

FrameLayoutを使っていた部分を上記で作ったListViewItemに置き換えます。

<?xml version="1.0" encoding="utf-8"?><com.example.kazuki.myapplication.ListViewItemxmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"><TextViewandroid:layout_width="fill_parent"android:layout_height="fill_parent"android:text="New Text"android:id="@+id/textView"/></com.example.kazuki.myapplication.ListViewItem>

実行して動作を確認

実行すると、真ん中の以外はいい感じに半透明になってるのがわかります。

f:id:okazuki:20140801191833p:plain

Android Wearで通知に丸いボタンをつける

$
0
0

Android Wearらしいあの丸いボタンのつけ方です。

NotificationのextendメソッドにNotification.WearableExtenderを突っ込みます。このWearableExtenderに対して、addActionメソッドでNotification.Actionを渡します。Notification.Actionには、アイコンと、文字列と、PendingIntentを渡す感じでOKです。

早速コード。

PendingIntent i = PendingIntent.getActivity(this, 0, new Intent(this, MyActivity.class), PendingIntent.FLAG_UPDATE_CURRENT);
Notification.Builder b = new Notification.Builder(this)
        .setContentTitle("title")
        .setContentText("text")
        .setSmallIcon(R.drawable.ic_launcher)
        .extend(new Notification.WearableExtender()
            .addAction(new Notification.Action(R.drawable.ic_launcher, "launch", i)));
NotificationManager nm = (NotificationManager) this.getSystemService(Context.NOTIFICATION_SERVICE);
nm.notify(0, b.build());

実行するとこんな感じの通知が出る。

f:id:okazuki:20140801205457p:plain

横にスライドするとAndroid Wearっぽい丸いボタンが。ってかエミュレータ画面狭いのかはみ出てますね…。実機では、画面内に収まってるのに。

f:id:okazuki:20140801205559p:plain

Android Wearで通知を出したら即通知を表示する方法

$
0
0

いたってシンプル。通知を出したらアプリを閉じればいいのです。

アクティビティあたりで以下のような雰囲気のコードを書けばOK.

Notification n = ...組み立て処理...;
NotificationManager nm = (NotificationManager) this.getSystemService(Context.NOTIFICATION_SERVICE);
nm.notify(0, n);
this.finish();

めとべや東京5の発表資料 #めとべや

ストアアプリの単体テストプロジェクトで出力ディレクトリにコピーしたファイルを読み込む

$
0
0

サンプルデータとかを読み込みたいらしいです。

例えばテストプロジェクトに、TextFile1.txtという名前のファイルを作って中身を以下のようにします。

Hello world

コンテンツにして、出力フォルダにコピーするようにプロパティを構成します。

f:id:okazuki:20140803152934p:plain

このファイルを単体テスト内で読み込むには、Package,Current.InstallLocationのGetFileAsyncを使ってStorageFileを取得してごにょごにょします。とりあえずファイル内の文字列をアサートするならこんな感じ。

using Microsoft.VisualStudio.TestPlatform.UnitTestFramework;
using System;
using System.IO;
using System.Threading.Tasks;
using Windows.ApplicationModel;

namespace UnitTestLibrary1
{
    [TestClass]
    publicclass UnitTest1
    {
        [TestMethod]
        public async Task TestMethod1()
        {
            var file = await Package.Current.InstalledLocation.GetFileAsync("TextFile1.txt");
            Assert.IsNotNull(file);

            using (var r = await file.OpenReadAsync())
            using (var s = new StreamReader(r.AsStream()))
            {
                Assert.AreEqual("Hello world", (await s.ReadToEndAsync()).Trim());
            }
        }
    }
}

Xamarin.AndroidでFragment使ってみた

$
0
0

時代はFragmentらしいので、使ってみました。MainActivityはFragmentをホストするためのFrameLayoutを置いてるだけのシンプル構造。

Main.axml

<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:orientation="vertical"android:layout_width="fill_parent"android:layout_height="fill_parent"android:minWidth="25px"android:minHeight="25px"><FrameLayoutandroid:minWidth="25px"android:minHeight="25px"android:layout_width="match_parent"android:layout_height="match_parent"android:id="@+id/Container" /></LinearLayout>

今回はFragmentを2つ切り替えるようにしてみようと思うので、layoutにFragment1.axmlとFragment2.axmlを作りました。どちらもTextViewを置いただけの必要最低限です。

Fragment1.axml

<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:orientation="vertical"android:layout_width="fill_parent"android:layout_height="fill_parent"android:minWidth="25px"android:minHeight="25px"android:padding="10dp"><TextViewandroid:text="Fragment1"android:textAppearance="?android:attr/textAppearanceLarge"android:layout_width="match_parent"android:layout_height="wrap_content"android:id="@+id/textView1" /></LinearLayout>

Fragment2.axml

<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:orientation="vertical"android:layout_width="fill_parent"android:layout_height="fill_parent"android:minWidth="25px"android:minHeight="25px"><TextViewandroid:text="Fragment2"android:textAppearance="?android:attr/textAppearanceLarge"android:layout_width="match_parent"android:layout_height="wrap_content"android:id="@+id/textView1"android:padding="10dp" /></LinearLayout>

このレイアウトを読み込むFragmentを作ります。今回はViewをレイアウトファイルから読み込むだけのシンプルなやつにしました。必要最低限って大事ですよね。

class Fragment1 : Fragment
{
    publicoverride View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
    {
        return inflater.Inflate(Resource.Layout.Fragment1, container, false);
    }
}

class Fragment2 : Fragment
{
    publicoverride View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
    {
        return inflater.Inflate(Resource.Layout.Fragment2, container, false);
    }
}

最後にMainActivityを作りこんでいきます。MainActivityにメニューを2つ追加します。こいつがタップされたら、それぞれFragment1とFragment2を画面に表示するようにしてみたいと思います。

publicoverridebool OnCreateOptionsMenu(IMenu menu)
{
    menu.Add(0, 0, 0, Resource.String.Menu1Text);
    menu.Add(0, 1, 1, Resource.String.Menu2Text);
    returntrue;
}

第二引数がIDになります。このIDをもとにOnMenuItemSelectedでswitch文を書きます。

publicoverridebool OnMenuItemSelected(int featureId, IMenuItem item)
{
    switch (item.ItemId)
    {
        case0:
            var f1 = this.FragmentManager.FindFragmentByTag<Fragment1>(typeof(Fragment1).Name);
            if (f1 == null)
            {
                this.FragmentManager
                    .BeginTransaction()
                    .Replace(Resource.Id.Container, new Fragment1(), typeof(Fragment1).Name)
                    .AddToBackStack(null)
                    .Commit();
            }
            else
            {
                this.FragmentManager
                    .BeginTransaction()
                    .Replace(Resource.Id.Container, f1)
                    .AddToBackStack(null)
                    .Commit();
            }
            break;
        case1:
            var f2 = this.FragmentManager.FindFragmentByTag<Fragment2>(typeof(Fragment2).Name);
            if (f2 == null)
            {
                this.FragmentManager
                    .BeginTransaction()
                    .Replace(Resource.Id.Container, new Fragment2(), typeof(Fragment2).Name)
                    .AddToBackStack(null)
                    .Commit();
            }
            else
            {
                this.FragmentManager
                    .BeginTransaction()
                    .Replace(Resource.Id.Container, f2)
                    .AddToBackStack(null)
                   .Commit();
            }
            break;
        default:
            thrownew Java.Lang.IllegalArgumentException();
    }
    returntrue;
}

タグ名(ここではクラス名をタグ名にしてます)で検索してなかったら新規に作ったものをFrameLayoutに設定して、みつかったら、みつかったやつをFrameLayoutに設定しています。

実行すると、Menu1を押すとFragment1が表示されてMenu2を押すとFragment2が表示されます。戻るボタンで戻るにも対応しています(AddToBackStackがそれ)。

Xamarin.AndroidでDialogFragmentを使う

$
0
0

時代はダイアログもフラグメント…ということでやってみました。簡単なアラートダイアログを出して、押したボタンに応じてトーストを表示してます。

using Android.App;
using Android.Content;
using Android.OS;
using Android.Widget;

namespace DialogFragmentApp
{
    [Activity(Label = "DialogFragmentApp", MainLauncher = true, Icon = "@drawable/icon")]
    publicclass MainActivity : Activity
    {

        protectedoverridevoid OnCreate(Bundle bundle)
        {
            base.OnCreate(bundle);

            this.SetContentView(Resource.Layout.Main);
            var button = this.FindViewById<Button>(Resource.Id.MyButton);

            button.Click += (s, e) =>
            {
                // タグ名でフラグメントを取得してみる
                var dlg = this.FragmentManager
                    .FindFragmentByTag<MyDialogFragment>(typeof(MyDialogFragment).Name);
                if (dlg == null)
                {
                    // 取得できなかったらBundle軽油でメッセージを渡してダイアログを表示
                    Bundle b = new Bundle();
                    b.PutString("alertMessage", "Hello world");
                    dlg = new MyDialogFragment();
                    dlg.Arguments = b;
                    dlg.Show(this.FragmentManager, typeof(MyDialogFragment).Name);
                }
            };
        }
    }

    // ダイアログのフラグメント。// ボタンを押したときの処理に応答するためIDialogInterfaceOnClickListenerを実装class MyDialogFragment : DialogFragment, IDialogInterfaceOnClickListener
    {
        publicoverride Dialog OnCreateDialog(Bundle savedInstanceState)
        {
            // ダイアログを作って返す
            var d = new AlertDialog.Builder(this.Activity)
                .SetTitle("title")
                .SetMessage(this.Arguments.GetString("alertMessage"))
                // OKボタンを押したときのコールバックの設定
                .SetPositiveButton("OK", this)
                // Cancelボタンを押したときのコールバックの設定
                .SetNegativeButton("Cancel", this)
                .Create();
            return d;
        }

        publicvoid OnClick(IDialogInterface dialog, int which)
        {
            // トーストを作って表示
            Toast.MakeText(
                this.Activity,
                // whichがPositiveなときとそうじゃないときで表示テキストを切り替える
                which == (int)DialogButtonType.Positive ? "Positive" : "Negative",
                ToastLength.Long)
                .Show();
        }
    }

}

まぁコメント通りです。OnClick内でActivityに結果返すのどうするんだろう????


AndroidからAndroidWearのアプリを起動する

$
0
0

基本的には、これに準じます。

上記ページがAndroid Wear → AndroidなのをAndroid → Android WearにしてやればOK。

Android Studioでプロジェクトを作ってMobile側のアプリでメニューのSettingsをタップしたときの処理あたりに、メッセージ送信処理を突っ込みます。

package com.example.kazuki.myapplication;

import android.app.Activity;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;

import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.wearable.MessageApi;
import com.google.android.gms.wearable.Node;
import com.google.android.gms.wearable.NodeApi;
import com.google.android.gms.wearable.Wearable;


publicclass MyActivity extends Activity {

    private GoogleApiClient client;

    @Overrideprotectedvoid onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_my);
        this.client = new GoogleApiClient.Builder(this)
                .addApi(Wearable.API)
                .build();
        this.client.connect();
    }


    @Overridepublicboolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.my, menu);
        returntrue;
    }

    @Overridepublicboolean onOptionsItemSelected(MenuItem item) {
        // Handle action bar item clicks here. The action bar will// automatically handle clicks on the Home/Up button, so long// as you specify a parent activity in AndroidManifest.xml.int id = item.getItemId();
        if (id == R.id.action_settings) {
            new Thread(new Runnable() {
                @Overridepublicvoid run() {
                    NodeApi.GetConnectedNodesResult nodes = Wearable.NodeApi.getConnectedNodes(client).await();
                    for (Node node : nodes.getNodes()) {
                        MessageApi.SendMessageResult r = Wearable.MessageApi.sendMessage(
                                client,
                                node.getId(),
                                "/path",
                                "Hello world".getBytes()
                        ).await();
                    }
                }
            }).start();
            returntrue;
        }
        returnsuper.onOptionsItemSelected(item);
    }
}

GoogleApiClientを使うのでAndroidManifest.xmlに以下の定義を追加します。

<meta-dataandroid:name="com.google.android.gms.version"android:value="@integer/google_play_services_version" />

Android Wear側では、WearableListenerServiceを継承したサービスを作って、メッセージを受信したらIntentを使ってActivityを起動するようにします。サービスから起動するのでIntentのフラグにFLAG_ACTIVITY_NEW_TASKを追加しておきます。

package com.example.kazuki.myapplication;

import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.util.Log;

import com.google.android.gms.wearable.MessageEvent;
import com.google.android.gms.wearable.WearableListenerService;

publicclass MyService extends WearableListenerService {
    public MyService() {
    }

    @Overridepublicvoid onMessageReceived(MessageEvent messageEvent) {
        Intent i = new Intent(this, MyActivity.class);
        i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        this.startActivity(i);
    }
}

サービスの定義をしておきます。intent-filterが特殊なので忘れずに。

<serviceandroid:name=".MyService"><intent-filter><action android:name="com.google.android.gms.wearable.BIND_LISTENER" /></intent-filter></service>

adbでよく使うshellとpush

$
0
0

adb shellでシェルへつなぐ。

adb push ローカル リモートでファイル転送。

Androidでファイルの読み書きを行う

$
0
0

openFileOutputとopenFileInputで読み書きできる。getFilesDirで保存されてるファイルの入ってるフォルダが取れる。

publicvoid onWrite(View v) {
     PrintWriter w = null;
     try {
         w = new PrintWriter(openFileOutput("out.txt", MODE_PRIVATE));
         w.println("Hello world");
         w.close();
     } catch (FileNotFoundException e) {
         e.printStackTrace();
     }
 }
 publicvoid onRead(View v) {
     try {
         BufferedReader r = new BufferedReader(new InputStreamReader(openFileInput("out.txt")));
         Toast.makeText(this, r.readLine(), Toast.LENGTH_SHORT).show();
         r.close();
     } catch (IOException e) {
         e.printStackTrace();
     }
 }
 publicvoid onShow(View v) {
     String text = "";
     for (File f : getFilesDir().listFiles()) {
         text += f.getName() + ", ";
     }
     Toast.makeText(this, text, Toast.LENGTH_SHORT).show();
 }

リソース管理の観点と、文字列連結の観点からは褒められたコードじゃないけどとりあえずね。

BaseAdapterを継承しよう

$
0
0

ListViewにデータを表示するためにAdapterを使いますが、こいつを実装してみたいとおもいます。

とりあえず、前提として以下のようなクラスを画面に表示するとします。

package com.example.kazuki.myapplication.data;

import android.provider.BaseColumns;

/** * Created by Kazuki on 2014/08/09. */publicclass LogItem {
    privatelong id;
    private String timestamp;
    private String message;

    publiclong getId() {
        return id;
    }

    publicvoid setId(long id) {
        this.id = id;
    }

    public String getTimestamp() {
        return timestamp;
    }

    publicvoid setTimestamp(String timestamp) {
        this.timestamp = timestamp;
    }

    public String getMessage() {
        return message;
    }

    publicvoid setMessage(String message) {
        this.message = message;
    }
}

このクラスを表示するためのレイアウトファイルを準備します。ここではlayout_log_item.xmlという名前で作りました。

<?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"><TextViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:textAppearance="?android:attr/textAppearanceLarge"android:text="Large Text"android:id="@+id/textView_message" /><TextViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:textAppearance="?android:attr/textAppearanceSmall"android:text="Small Text"android:id="@+id/textView_timestamp" /></LinearLayout>

Adapterクラスは、BaseAdapterクラスを継承して作ります。コンストラクタでLayoutInflater作るためのContextと、データのリストを受け取るようにしています。

package com.example.kazuki.myapplication;

import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.BaseAdapter;
import android.widget.TextView;

import com.example.kazuki.myapplication.data.LogItem;

import java.util.List;

/** * Created by Kazuki on 2014/08/09. */publicclass LogItemAdapter extends BaseAdapter {
    private LayoutInflater inflater;
    private List<LogItem> items;

    public LogItemAdapter(Context context, List<LogItem> objects) {
        this.inflater = LayoutInflater.from(context);
        this.items = objects;
    }

    @Overridepublicint getCount() {
        returnthis.items.size();
    }

    @Overridepublic Object getItem(int position) {
        returnthis.items.get(position);
    }

    @Overridepubliclong getItemId(int position) {
        returnthis.items.get(position).getId();
    }

    @Overridepublic View getView(int position, View convertView, ViewGroup parent) {
        LogItem item = this.items.get(position);
        if (convertView == null) {
            convertView = this.inflater.inflate(R.layout.layout_log_item, null);
        }
        TextView message = (TextView) convertView.findViewById(R.id.textView_message);
        TextView time = (TextView) convertView.findViewById(R.id.textView_timestamp);
        message.setText(item.getMessage());
        time.setText(item.getTimestamp());
        return convertView;
    }
}

getCount, getItem, getItemIdは、抽象メソッドなのでさくっと実装しておきます。getViewでは、convertViewがnullのときは、inflaterを使ってViewを作ります。そして、データを必要な個所に設定していきます。めんどくさいですね。

使う時は、データとAdapterを用意して、ListViewとかにsetAdapterしてやればOKです。

Loaderを使ったデータの読み込み

$
0
0

最近のAndroidでのデータの読み込みはLoaderというものを使うらしいです。ちょっとやってみました。

オレオレ仕様にカスタムできるLoader

カーソル前提のLoaderもあるんですが、自前のデータ読み込み処理を書けるAsyncTaskLoaderというのがあるのでそれを使うのが一番柔軟っぽいです。

AsyncTaskLoaderは、継承してloadInBackgroundメソッドを実装するだけの簡単クラスです。例えばPersonという以下のようなクラスがあるとします。

// めんどいのでActivityの内部クラスにしたpublicstaticclass Person {
    private String name;
    public String getName() {
        return name;
    }
    publicvoid setName(String name) {
        this.name = name;
    }
    @Overridepublic String toString() {
        returnthis.name;
    }
}

適当にこのクラスを生成する実装は以下のようになります。画面にeditTextという名前のEditTextを置いていて、そいつから取得したテキストをベースに先ほどのPersonクラスを組み立てては、Listに突っ込んでいます。時間のかかる処理っぽく少しSleepも入れています。

final Random r = new Random();
final String input = this.editText.getText().toString();
AsyncTaskLoader<List<Person>> loader = new AsyncTaskLoader<List<Person>>(this) {
    @Overridepublic List<Person> loadInBackground() {
        Log.d("MyActivity", "loadInBackground");
        int count = r.nextInt(100);
        List<Person> result = new ArrayList<Person>();
        for (int i = 0; i < count; i++) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            Person p = new Person();
            p.setName(input + " " + i);
            result.add(p);
        }
        return result;
    }
};

Loaderを使うためのコールバックインターフェース

Javaってコールバック地獄でめんどくさいですよね…。Loaderをバックグラウンドで実行するために、以下のインターフェースを実装しないといけません。これは先ほど示したAsyncTaskLoaderクラスとは別ものです。LoaderManager.LoaderCallbacksというインターフェースになります。Tは、AsyncTaskLoaderのTと合わせて使います。

LoaderCallbacksには、以下の3つのメソッドがあります。

  • onCreateLoader: Loaderを作るメソッド
  • onLoadFinished: Loaderの処理が終わった後に呼ばれるメソッド
  • onLoaderReset: リセットしたときに呼ばれるメソッド

ActivityにLoaderCallbacksインターフェースを実装するサンプルをよく見るのでここでもそうしました。

まず、onCreateLoaderメソッドです。こいつはさっきのAsyncTaskLoaderを作成しています。 そして、Loaderの処理をキックするforceLoadメソッドを呼んでいます(大事)

@Overridepublic Loader<List<Person>> onCreateLoader(int id, Bundle args) {
    Log.d("MyActivity", "onCreateLoader");
    Random r = new Random();
    final String input = this.editText.getText().toString();
    AsyncTaskLoader<List<Person>> loader = new AsyncTaskLoader<List<Person>>(this) {
        @Overridepublic List<Person> loadInBackground() {
            Log.d("MyActivity", "loadInBackground");
            int count = r.nextInt(100);
            List<Person> result = new ArrayList<Person>();
            for (int i = 0; i < count; i++) {
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                Person p = new Person();
                p.setName(input + " " + i);
                result.add(p);
            }
            return result;
        }
    };
    loader.forceLoad();
    return loader;
}

そして、onLoadFinishedメソッドで読み込みが終わったときの処理を書きます。ここではArrayAdapterにデータをセットしています。

@Overridepublicvoid onLoadFinished(Loader<List<Person>> loader, List<Person> data) {
    Log.d("MyActivity", "onLoadFinished");
    this.peopleAdapter.clear();
    this.peopleAdapter.addAll(data);
}
@Overridepublicvoid onLoaderReset(Loader<List<Person>> loader) {
}

ついでに、何もしないonLoaderResetも書いておきました。

コード全体

レイアウトファイル。EditTextとListView置いただけの画面

<?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"><EditTextandroid:layout_width="match_parent"android:layout_height="wrap_content"android:id="@+id/editText" /><ListViewandroid:layout_width="match_parent"android:layout_height="wrap_content"android:id="@+id/listView" /></LinearLayout>

Activityに全部処理かいてます///

package com.example.kazuki.myapplication;

import android.app.Activity;
import android.app.LoaderManager;
import android.content.AsyncTaskLoader;
import android.content.Loader;
import android.os.Bundle;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.ArrayAdapter;
import android.widget.EditText;
import android.widget.ListView;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;


publicclass MyActivity extends Activity implements LoaderManager.LoaderCallbacks<List<MyActivity.Person>>  {

    private EditText editText;
    private ListView listView;
    private ArrayAdapter<Person> peopleAdapter;


    @Overrideprotectedvoid onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_my);
        this.editText = (EditText) this.findViewById(R.id.editText);
        this.listView = (ListView) this.findViewById(R.id.listView);
        this.peopleAdapter = new ArrayAdapter<Person>(this, android.R.layout.simple_list_item_1);
        this.listView.setAdapter(this.peopleAdapter);
    }


    @Overridepublicboolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.my, menu);
        returntrue;
    }

    @Overridepublicboolean onOptionsItemSelected(MenuItem item) {
        // Handle action bar item clicks here. The action bar will// automatically handle clicks on the Home/Up button, so long// as you specify a parent activity in AndroidManifest.xml.int id = item.getItemId();
        if (id == R.id.action_settings) {
            Log.d("MyActivity", "onOptionsItemSelected");
            this.getLoaderManager().restartLoader(0, null, this);
            returntrue;
        }
        returnsuper.onOptionsItemSelected(item);
    }


    @Overridepublic Loader<List<Person>> onCreateLoader(int id, Bundle args) {
        Log.d("MyActivity", "onCreateLoader");
        final Random r = new Random();
        final String input = this.editText.getText().toString();
        AsyncTaskLoader<List<Person>> loader = new AsyncTaskLoader<List<Person>>(this) {
            @Overridepublic List<Person> loadInBackground() {
                Log.d("MyActivity", "loadInBackground");
                int count = r.nextInt(100);
                List<Person> result = new ArrayList<Person>();
                for (int i = 0; i < count; i++) {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    Person p = new Person();
                    p.setName(input + " " + i);
                    result.add(p);
                }
                return result;
            }
        };
        loader.forceLoad();
        return loader;
    }

    @Overridepublicvoid onLoadFinished(Loader<List<Person>> loader, List<Person> data) {
        Log.d("MyActivity", "onLoadFinished");
        this.peopleAdapter.clear();
        this.peopleAdapter.addAll(data);
    }

    @Overridepublicvoid onLoaderReset(Loader<List<Person>> loader) {

    }


    publicstaticclass Person {
        private String name;

        public String getName() {
            return name;
        }

        publicvoid setName(String name) {
            this.name = name;
        }

        @Overridepublic String toString() {
            returnthis.name;
        }
    }
}
Viewing all 1388 articles
Browse latest View live


<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>