投稿者「zawata」のアーカイブ

【iOS】UIViewをタッチして移動させる

使用環境
Xcode:8.0
Swift:3.0

UIViewをタッチして移動させてみよう。完成イメージは以下の通りである。
20161022_viewtouchmove
※クリックでGIFアニメーションを表示

UIViewControllerが用意したデフォルトのUIViewにサブビューを追加し、赤いビューと青いビューをタッチして動かせるようになっている。

準備

Xcodeを起動したら「Single View Application」テンプレートで新しいプロジェクトを作成する。

実装

プロジェクト内にあるViewController.swiftに処理を書いていく。
以下にコードを示す。

ViewController.swift

import UIKit

// ユーティリティメソッド CGPoint同士の足し算を+で書けるようにする
func -(_ left:CGPoint, _ right:CGPoint)->CGPoint{
    return CGPoint(x:left.x - right.x, y:left.y - right.y)
}
// ユーティリティメソッド CGPoint同士の引き算を-で書けるようにする
func +(_ left:CGPoint, _ right:CGPoint)->CGPoint{
    return CGPoint(x:left.x + right.x, y:left.y + right.y)
}

class ViewController: UIViewController {

    // タッチ開始時のUIViewのorigin
    var orgOrigin: CGPoint!
    // タッチ開始時の親ビュー上のタッチ位置
    var orgParentPoint : CGPoint!
    
    // Viewのパンジェスチャーに反応し、処理するためのメソッド
    func handlePanGesture(sender: UIPanGestureRecognizer){
        switch sender.state {
        case UIGestureRecognizerState.began:
            // タッチ開始:タッチされたビューのoriginと親ビュー上のタッチ位置を記録しておく
            orgOrigin = sender.view?.frame.origin
            orgParentPoint = sender.translation(in: self.view)
            break
        case UIGestureRecognizerState.changed:
            // 現在の親ビュー上でのタッチ位置を求める
            let newParentPoint = sender.translation(in: self.view)
            // パンジャスチャの継続:タッチ開始時のビューのoriginにタッチ開始からの移動量を加算する
            sender.view?.frame.origin = orgOrigin + newParentPoint - orgParentPoint
            break
        default:
            break
        }
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // ビューを2つ作成し、Subviewとして追加する
        // !!注意!!
        // UIGestureRecognizerのインスタンスは複数のViewで使いまわせないので、各View毎に作成する
        let view1 = UIView(frame: CGRect(x:10,y:20,width:100,height:50))
        view1.backgroundColor = UIColor.red
        view1.addGestureRecognizer(
            UIPanGestureRecognizer(target:self, action:#selector(handlePanGesture)))
        self.view.addSubview(view1)
        
        let view2 = UIView(frame: CGRect(x:10,y:100,width:100,height:50))
        view2.backgroundColor = UIColor.blue
        view2.addGestureRecognizer(
            UIPanGestureRecognizer(target:self, action:#selector(handlePanGesture)))
        self.view.addSubview(view2)
        
    }
}

アプリ起動時にまず呼ばれるのは、38行目からのviewDidLoadである。
ここでは背景色が赤と青のビューを一つずつ作成し、親にサブビューとして追加している。

これらのビューでタッチイベントを受けられるようにするため、UIGestureRecognizerの一種であるUIPanGestureRecognizerをサブビューに追加する。これはパンジェスチャ、すなわちタッチ後にドラッグするような操作を認識するためのRecognizerである。
ここで注意が必要なのはGestureRecognizerのインスタンスは複数のUIViewで使い回せない、ということである。下のようについ書きたくなるのだが、このように書くとタッチが有効になるのは最後に追加したViewの方のみとなってしまう。

(GestureRecognizer追加時の失敗例)

let recognizer = UIPanGestureRecognizer(target: self, action: #selector(handlePanGesture))
let view1 = UIView()
view1.addGestureRecognizer(recognizer)

// こちらのViewだけがタッチを受け取る
let view2 = UIView()
view2.addGestureRecognizer(recognizer)

タッチ時の処理を扱うのは20行目からのhandlePanGestureメソッドである。ここで行っているのは以下の処理である。

  • タッチ開始時:タッチされたビューの位置(origin)と親ビュー上のタッチ位置を記録しておく。
  • タッチ移動時:タッチ開始時点のビューの位置(origin)に、タッチ開始点から現在のタッチまでの移動量を足してビューの新しい位置(origin)とする。

タッチの移動量は
「現在の親ビューにおけるタッチ位置」-「タッチ開始時点の親ビューにおける位置」
で求めることができる。サブビューの位置はタッチの移動によってどんどん変わっていくから、計算には親ビュー上のタッチ位置の値を使うことがポイントである。

31行目でCGPointに対して+や-の演算子を使っている。本来はCGPointにこのような演算子はないのだが、4行目、6行目でユーティリティメソッドを用意しているのでこのように書くことができる。これは、ソースコードを見やすくするための工夫である。もし、普通に書くならば以下のようになるだろう。

sender.view?.frame.origin = 
    CGPoint(x:(orgOrigin.x + newParentPoint.x - orgParentPoint.x,
            y:(orgOrigin.y + newParentPoint.y - orgParentPoint.y ))

CGPointのユーティリティメソッドについて、さらに詳しく知りたいときは以下の記事が参考になる。
http://qiita.com/koher/items/7b4f23d749d973d409c9

さて、今回の内容は以前にAndroidでもやったことがある。Androidでの実装に興味がある方は以下の記事も参考にしてほしい。
http://zawapro.com/?p=305


【iOS】PlaygroundでUIViewを表示する

使用環境
Xcode:8.0
Swift:3.0

XcodeにはPlaygoundという機能が搭載されている。
iOSアプリ開発の場合、エミュレータや実機を立ち上げることなく書いたコードの実行結果がすぐに表示されるので、便利である。そこで、今回はUIViewをPlaygroundで表示させてみよう。

準備

Xcodeを起動したら File -> New -> Playground を選択し、新規Playgroundを作成する。

実装

作成されたPlaygroundに以下のように入力する。
とりあえず、UIViewを表示させたいので以下のようにした。

MyPlayground.playground

import UIKit
import PlaygroundSupport

// UIViewを生成する。大きさはPlaygroundなので適当
let view = UIView(frame: CGRect(x:0, y:0, width:360, height:480))
// 背景に薄灰色を表示
view.backgroundColor = UIColor.lightGray
// ライブビューにセット
PlaygroundPage.current.liveView = view

2行目 PlaygroundSupportというフレームワークをインポートしている。これは、9行目に出てくるPlaygroundPageを使うためのもので、かつてはXCPlaygroundという名前だった。
4-7行目 UIViewを生成する。大きさは適当な値を与えている。プロトタイピングの段階ではこれでいいだろう。また、実行結果を分かりやすくするため、背景色には薄い灰色を指定した。
9行目 UIViewを表示させるためのコードである。直前で作ったUIViewをliveViewプロパティにセットする。

ここまで入力できたら、アシスタントビューを表示させてみよう。アシスタントビューは右上の丸が二つ重なっているアイコンをクリックすると表示できる。
assistantview

うまくいくと、アシスタントビューにUIViewが表示される。
playgdound


【Android】Fragmentを使う(1) レイアウトに直接配置する

Fragmentを効果的に使うことで、画面構成が柔軟になり、UI部分のコードの再利用がしやすくなる。
しかし、使い方を理解するには少し複雑である。
そこで、Fragmentの使い方について、小さなところから順に見ていくことにする。
今回紹介するのは「Fragmentをレイアウトにタグで直接配置する方法」である。

まずはAndroidStudioで新しいプロジェクトを作る。
プロジェクトのテンプレートは、「Empty Activity」とする。
プロジェクトが作成されたら、以下の手順で進めていく。

1.FragmentのためのUIレイアウトを作る
まずは、FragmentのためのUIレイアウトを作る。
my_fragment.xmlという名前でres/layout下に保存し、内容は適当に以下のようにした。

<?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"
    android:background="#ea6c6c">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="This is my fragment!"
        android:id="@+id/textView"
        android:layout_alignParentTop="true"
        android:layout_alignParentLeft="true"
        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/textView"
        android:layout_alignParentLeft="true"
        android:layout_alignParentStart="true" />
</RelativeLayout>

テキストとボタンを配置しただけの単純なレイアウトである。
配置された場所を分かりやすくするため、全体の背景色を赤系にしてある。

2.Fragmentを継承したクラスを作る
次は、Fragmentのためのソースを作成する。
ソースコードは以下のようになる。

import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

public class MyFragment extends Fragment {
    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater,
                             @Nullable ViewGroup container,
                             @Nullable Bundle savedInstanceState) {
        // 引数で渡ってきたinflaterを使ってレイアウトをinfrateする
        final View layout = inflater.inflate(R.layout.my_fragment, container);

        // 必要に応じてUIにコールバックを設定する
        layout.findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Toast.makeText(getContext(), "Hello Fragment!!", Toast.LENGTH_SHORT).show();
            }
        });

        // infrateされたViewをそのまま返す
        return layout;
    }
}

注意が必要なのは、android.app.Fragmentではなく、android.support.v4.app.Fragmentをインポートしているところである。(3行目)
これは、サポートライブラリに含まれるFragmentである。
あえてこちらのパッケージを使う理由は、Androidのバージョンが低い端末向けにアプリを配信する場合でも、最新の機能が反映されたFragmentを使えるからである。
apkのサイズが大きくなってしまうのとトレードオフだが、特に理由が無ければサポートライブラリ版を使ったほうが良いと思う。

さて、実装については今回は必要最低限とするため、onCreateView()のオーバーライドのみとした。
引数で渡ってくるinfratorを使ってレイアウトをinfrateしてやり、必要に応じてUIにコールバックをセットした後、infrateされたViewを返すだけである。
以上がFragmentの作成である。

3.メインのレイアウトにFragmentを配置する
Activityが読み込むためのレイアウトファイルに、fragmentタグを使って直接配置する。
ソースは以下のようになる。

<?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="com.zawapro.fragmentlesson.MainActivity">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello World!"
        android:id="@+id/textView2" />

    <fragment
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:name="com.zawapro.fragmentlesson.MyFragment"
        android:id="@+id/fragment"
        android:layout_below="@+id/textView2"
        android:layout_alignParentLeft="true"
        android:layout_alignParentStart="true"
        android:layout_alignParentBottom="true"
        android:layout_alignParentRight="true"
        android:layout_alignParentEnd="true" />
</RelativeLayout>

ハイライトした18行目がFragmentを配置している個所である。
このように、普通のViewを配置する方法と何ら変わりは無いことが分かる。

4.Activityのコード
Activityのコードは以下のようになる。今回はAndroidStudioが自動生成したものに手を加えていない。
サポートライブラリを使うため、AppCompatActivity となっている点に気を付けておけば良いだろう。

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

public class MainActivity extends AppCompatActivity {

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

以上でアプリは完成である。
実行すると以下のようになる。
device-2016-04-21-231706

ところで、配置したFragmentはどのようにViewに置き換わっているのだろうか?
Viewのヒエラルキーを見ると以下のようになっている。
hierarchy
activity_main.xmlで宣言したfragmentの部分が、そのままmy_fragment.xmlの内容に置き換わっているのが分かる。
まずは「FragmentとはViewのまとまりをコンポーネント化する仕組みである」ということをおさえておくと良いと思う。

さて、とは言うものの、実はレイアウトファイルにタグで直接Fragmentを配置する方法は固定的であまり融通が利かないのである。
例えば、画面上のFragmentを動的に入れ替えたり、バックスタックに乗せたり、ということをするにはそのためのコードを書いていく必要がある。
そういった意味では今回紹介した方法ではあまりFragmentにする恩恵を受けているとは言えず、
実際ほとんど使われていないのではないか、という気がする。
しかし、入門編としてはこのくらいの内容が良いだろう。


【Android】ndk-buildの実行時に情報を出力する

NDKのソースをndk-buildコマンドでビルドをするとき、Android.mkに書いた内容が間違っていてビルドが上手くいかないことがある。
こうしたときはエラーメッセージを調べつつ原因を探していくことになるのだが、ビルドがどこまで進んだか、とかビルド中に使われた変数の値がどうなっているか、などが分かれば間違った原因を探るための手掛かりになる。
そうした用途に便利なのが$(error), $(warning), $(info)である。これらは元々Makefileの関数だが、Android.mkでもそのまま使える。実際には以下のようにして使う。

$(error エラー時のメッセージ)
$(warning ワーニング時のメッセージ)
$(info メッセージ)

早速試してみよう。
まずは適当なフォルダを作り、以下のような構成にする。

{project_folder}
└── jni
    └── Android.mk

jniフォルダはNDKのデフォルトのソースフォルダで、本来はこの下にc/c++で書いたソースコードを置いていく。
このフォルダが無いと、ndk-buildを実行したときに「 Could not find application project directory !」というエラーとなってしまうので、とりあえず今は空のフォルダを作っておく。

Android.mkには以下の内容を記述する。

HOGE := this is hoge!

$(info This is info mesage! HOGE:$(HOGE))
$(warning This is warning mesage!)
$(error This is error mesage!)

上記では$(info)の例として、変数HOGEにセットされた値を表示している。
ファイルを保存したら、プロジェクトのルートフォルダでndk-buildコマンドを実行する。
結果は以下のようになる。

$ pwd
{project_folder}
$ ndk-build
This is info mesage! HOGE:this is hoge!
jni/Android.mk:4: This is warning mesage!
jni/Android.mk:5: *** This is error mesage!.  中止.

この実行結果から以下のことが分かる。
・infoは単にメッセージを出力する。
・warningはメッセージに加え、ファイル名と行数を表示する。
・errorはメッセージに加え、ファイル名と行数を表示するとともに、その個所で停止する。

これらの関数を効果的に使うことで、不具合が起きている個所を特定するのが容易になるだろう。


【Android】ScaleGestureDetectorを使う

ピンチインとピンチアウトによって画像を拡大縮小させたいときは
ScaleGestureDetectorを使うと便利である。
今回はこのクラスの使い方を紹介する。

始めに、サンプルアプリの画面を示す。

device-1
device-2

画面の中央に青い四角が表示されている。これを、ピンチインとピンチアウトで拡大縮小できるようにする。

それでは、ソースコードを示す。
以下は、今回の処理を行うために作ったカスタムビューのソースコードである。
このビューの処理概要は以下の通り。
・タッチイベントを受け、それをScaleGestureDetectorに渡す
・ピンチインとピンチインの動作に応じて倍率を決定し、画面の中央に正方形を表示する
MyView.java

public class MyView extends View {
    private static final int RECT_SIZE = 64;

    private float mScale = 1.0f; // 描画する倍率
    private ScaleGestureDetector mScaleDetector;

    private Paint mRectPaint = new Paint();
    private Paint mTextPaint = new Paint();
    {
        mRectPaint.setStyle(Paint.Style.STROKE);
        mRectPaint.setColor(Color.BLUE);
        mRectPaint.setStrokeWidth(2.0f);

        mTextPaint.setTextSize(64.0f);
    }

    public MyView(Context context) {
        super(context);

        mScaleDetector = new ScaleGestureDetector(context, 
                new ScaleGestureDetector.OnScaleGestureListener() {
            @Override
            public boolean onScale(ScaleGestureDetector detector) {
                // ピンチイン・アウト中に継続して呼び出される
                // getScaleFactor()は
                // 『今回の2点タッチの距離/前回の2点タッチの距離』を返す
                Log.d("MyView", "MyView.onScale factor:" +
                 detector.getScaleFactor());

                // 表示倍率の計算
                mScale *= detector.getScaleFactor();
                invalidate();
                return true;
            }

            @Override
            public boolean onScaleBegin(ScaleGestureDetector detector) {
                Log.d("MyView", "MyView.onScaleBegin");
                return true;
            }

            @Override
            public void onScaleEnd(ScaleGestureDetector detector) {
                Log.d("MyView", "MyView.onScaleEnd");
            }
        });
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        // 受けたMotionEventを
        // そのままScaleGestureDetector#onTouchEvent()に渡す
        return mScaleDetector.onTouchEvent(event);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        // 現在の表示倍率を表示
        canvas.drawText("Scale:" + mScale, 0, 72, mTextPaint);

        // 正方形の1辺の長さを求める
        float rectSize = RECT_SIZE * mScale;

        // 描画位置をViewの中心にする
        float left = getWidth() / 2 - rectSize / 2;
        float top = getHeight() / 2 - rectSize / 2;
        float bottom = top + rectSize;
        float right = left + rectSize;

        // 正方形の描画
        canvas.drawRect(left, top, right, bottom, mRectPaint);
    }
}

20行目 コンストラクタでScaleGestureDetectorのインスタンスを作る。
第2引数でOnScaleGestureListenerを継承したクラスを渡している。
このクラスのオーバーライドメソッドであるonScale(),onScaleBegin(),onScaleEnd()に独自の実装を書くことによって、ピンチイン・ピンチアウトが行われたときの挙動を制御することができる。

23行目 OnScaleGestureListener#onScale()はピンチイン・アウト動作が行われると、継続して呼び出される。
 getScaleFactor()は前回のonScale呼び出し時と今回のonScale呼び出し時のタッチの距離の比率である。
 scaleFactor > 1.0 ならば拡大、scaleFactor < 1.0 ならば縮小となる。
 この比率を描画倍率に掛け、画面を更新する。

57行目~ 描画処理となる。
 Viewの中心に現在の倍率で正方形を描画している。

以上でピンチイン・アウトに対応して描画の拡大縮小を行う処理ができた。
最後に、参考のため上記のカスタムビューをActivityで使うときのコード例を示す。
Activity.java

public class MainActivity extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(new MyView(this));
    }

  ~ 略 ~
}

【シェル】パス文字列からディレクトリ部、ファイル名を取得する

シェルスクリプトを書いていて、
・パス文字列からディレクトリ部分だけを取得したい
・パス文字列からファイル名だけを取得したい
といったことは良くある。その方法を見ていこう。

パス文字列からディレクトリ部を取得する

・方法1 dirnameコマンドを使う

STR=/home/zawata/filename
echo $(dirname ${STR})

結果:/home/zawata

dirnameコマンドを使い、渡された文字列のディレクトリ部を取得するやり方である。

・方法2 パラメータ展開を使う

STR=/home/zawata/filename
echo ${STR%/*}

結果:/home/zawata

${parameter%word}と書くと、指定されたパラメータから
『wordのパターンに最短で後方一致する部分』を削除した値を得ることができる。
ここではwordに「/*」を指定しているので、パス文字列のうち、ファイル名部分がマッチする。

/home/zawata/filename ←赤文字部分がマッチし、削除される。

このようにすることで、ディレクトリ部を取得できる。

パス文字列からファイル名を取得する

・方法1 basenameコマンドを使う

STR=/home/zawata/filename
echo $(basename ${STR})

結果:filename
basenameコマンドを使い、渡された文字列のファイル名を取得できる。

・方法2 パラメータ展開を使う

STR=/home/zawata/filename
echo ${STR##*/}

結果:filename

${parameter##word}と書くと、指定されたパラメータから
『wordのパターンに最長で前方一致する部分』を削除した値を得ることができる。
ここではwordに「*/」を指定しているので、パス文字列のうち、ディレクトリ部分がマッチする。

/home/zawata/filename ←赤文字部分がマッチし、削除される。

このようにすることで、ファイル名を取得できる。

まとめ

以上をまとめたスクリプトを示す。

#!/bin/bash

STR="/home/zawata/filename"

# パス文字列からディレクトリ部を取得する
# 方法1. dirnameコマンドを使う
echo $(dirname ${STR})

# 方法2. パラメータ展開を使う
echo ${STR%/*}


# パス文字列からファイル名を取得する
# 方法1. basenameコマンドを使う
echo $(basename ${STR})

# 方法2. パラメータ展開を使う
echo ${STR##*/}

結果:
/home/zawata
/home/zawata
filename
filename

補足 パラメータ展開の覚え方

パラメータ展開の書き方は、記号の種類と数が色々とあってややこしい。
今回出てきたパラメータ展開の書き方に限っては、こんな風に覚えておくといいかもしれない。
・${parameter%word}の覚え方
パーセント記号はキーボード上で見ると「右側」にある。1つだけなので「短い」。
だから、「右側から最短で一致したパターン」を削除する。

・${parameter##word}の覚え方
シャープ記号はキーボード上で見ると「左側」にある。2つあるので「長い」。
だから、「左側から最長で一致したパターン」を削除する。


【Git】httpsでgit pushしようとしたらエラーが出た(Windows)

git push -u origin master
上記コマンドでhttpsでプッシュしようとしたら以下のようなエラーが出てしまった。

error: Protocol https not supported or disabled in libcurl while accessing https://***.***.***
fatal: HTTP request faild

※url部分は***.***.***に変えてある。

libcurlに原因があるようなのだが、どう解決したらいいのか、ちょっと分からない。
色々検索してみると、StackOverflowで以下のような投稿を見つけた。
http://stackoverflow.com/questions/17694502/libcurl-dll-error-with-git-push
この投稿の真ん中あたりに手順が書いてある。

  • gitのインストールフォルダにあるlibcurl.dllというファイルをコピーする。
    (たとえば、C:\Program Files\Git\bin\libcurl.dll)
  • このファイルをgit.exeがあるフォルダ(たとえば、C:\Program Files\Git\libexec\git-core)にペーストする。

私の場合はlibcurl-4.dllだったが、試してみたところエラーが出ることは無くなり、無事プッシュできた。


Category: Git

【謹賀新年】サイトリニューアル

2015年、あけましておめでとうございます。
これまで、プログラミングに関するtips的なブログをやっていたのですが、
昨年は時間がとれず、全く更新できませんでした。
そこで、年の初めに心機一転、ドメインもとってサイトをリニューアルすることにしました。
今年は、少しでもネタが書けるよう、がんばりたいと思います!
新生「ザワプロ!」をどうぞよろしくお願いいたします!

ざわ太 at zawapro.com
(旧blog lesson 01)


【Android】端末の向きによってレイアウトを切り替える

Androidでは端末の向きを回転すると、画面も自動的に回転するのが標準の動作である。
例えば、以下の様な挙動となる。

縦向き
横向き

しかし、横向きのときは専用のレイアウトで画面を配置し直したいこともある。
例えば、横画面は以下の様な配置にしたいときはどうすればよいだろうか?

このようにしたいときは、resフォルダ下にlayout-landという名前のフォルダを作り、切り替えたい元のレイアウトxmlと同じ名前のファイルを配置すればよい。以下の様になる。

最後に、縦向き用と横向き用のレイアウトxmlを示す。

activity_main.xml (layoutフォルダ下に配置)

<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"
    tools:context=".MainActivity" >
    <LinearLayout 
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentTop="true"
        android:orientation="horizontal"
        android:background="@android:color/background_dark">
        <Button
	        android:id="@+id/button1"
	        android:layout_width="0dp"
	        android:layout_height="wrap_content"
	        android:layout_weight="1"
	        android:text="Button1" /> 
        <Button
	        android:id="@+id/button2"
	        android:layout_width="0dp"
	        android:layout_height="wrap_content"
	        android:layout_weight="1"
	        android:text="Button2" />
        <Button
	        android:id="@+id/button3"
	        android:layout_width="0dp"
	        android:layout_height="wrap_content"
	        android:layout_weight="1"
	        android:text="Button3" /> 
    </LinearLayout>
</RelativeLayout>
activity_main.xml (layout-landフォルダ下に配置)

<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"
    tools:context=".MainActivity" >
    <LinearLayout 
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:layout_alignParentLeft="true"
        android:orientation="vertical"
        android:background="@android:color/background_dark">
        <Button
	        android:id="@+id/button1"
	        android:layout_width="wrap_content"
	        android:layout_height="0dp"
	        android:layout_weight="1"
	        android:text="Button1" /> 
        <Button
	        android:id="@+id/button2"
	        android:layout_width="wrap_content"
	        android:layout_height="0dp"
	        android:layout_weight="1"
	        android:text="Button2" />
        <Button
	        android:id="@+id/button3"
	        android:layout_width="wrap_content"
	        android:layout_height="0dp"
	        android:layout_weight="1"
	        android:text="Button3" /> 
    </LinearLayout>
</RelativeLayout>

ちなみに、layout-land下に配置したxmlのグラフィカルレイアウトは、eclipse上でちゃんと横画面として表示されるので、なかなか便利である。


【Android】カスタムビューにレイアウトxmlを適用する

Viewを継承した独自のカスタムビュー内のレイアウトをxmlで定義することができる。
レイアウトに関する事はJavaコードで書くよりもxmlで定義しておいたほうがコード量が減るし、後々メンテナンスしやすくなる。
今回はこの方法について見ていく。

まず、サンプルアプリの完成形となる画面を示す。

画面上部に表示された赤色の部分がカスタムビューとなっている。
テキストビューとその下に3つボタンがあって、それぞれのボタンを押すとテキストビューに押したボタンのテキストが表示されるようになっている。

まずは、このカスタムビューに適用するレイアウトxmlを示す。

myview.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:background="#FF8888" >
    <TextView 
        android:id="@+id/textView"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text=""/>
    <LinearLayout 
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        >
        <Button
            android:id="@+id/button1"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="button1"/>
        <Button
            android:id="@+id/button2"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="button2"/>
        <Button
            android:id="@+id/button3"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="button3"/>
    </LinearLayout>
</LinearLayout>

このxmlを見ると分かるとおり、内容は通常ActivityのsetContentView()で渡しているxmlの形式と変わらない。

次に、カスタムビューのJavaコード部分を示す。

MyView.java

package com.example.customviewlayoutlesson;

import android.content.Context;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.TextView;

// 適用するレイアウトxmlのトップレベルViewがLinearLayoutなので、ここでもLinearLayoutを継承する
public class MyView extends LinearLayout {

    public MyView(Context context, AttributeSet attrs) {
        super(context, attrs);

        // LayoutInflaterでレイアウトxmlの内容でViewを作る
        // LayoutInflater#inflate()の第2引数ではルートとなるViewとして自分自身を指定する
        View layout = LayoutInflater.from(context).inflate(R.layout.myview, this);
        final TextView textView = (TextView)layout.findViewById(R.id.textView);
        
        final View.OnClickListener onClickListener = new OnClickListener() {
            public void onClick(View v) {
                // 押されたボタンのテキストをTextViewに表示する
                textView.setText( ((Button)v).getText() );
            }
        };
        
        layout.findViewById(R.id.button1).setOnClickListener(onClickListener);
        layout.findViewById(R.id.button2).setOnClickListener(onClickListener);
        layout.findViewById(R.id.button3).setOnClickListener(onClickListener);
    }

}

本来、カスタムビューはViewを継承すれば良いのだが、今回はLinearLayoutを継承している。
これは、適用するxmlのトップレベルのViewがLinearLayoutだからで、その型を合わせる必要があるからである。
サンプルではLinearLayoutを例として取り上げたが、ほかにもViewGroupの子クラスのFrameLayoutやRelativeLayoutなどが使える。
19行目で、LayoutInflator#inflate()メソッドを使ってレイアウトxmlからViewを作っている。このメソッドの第2引数で、ルートとなるViewとして自分自身を指定する。こうすることでレイアウトxmlの内容がカスタムビューに適用される。

その後は、いつものActivity#onCreate()の中で行っている手順と同様、findViewById()メソッドで操作したいViewをレイアウトから取得できる。
今回のサンプルでは28-30行目でレイアウト内の3つのボタンにイベントリスナをセットしている。

最後にMain側のレイアウトxmlとActivityのソースを示す。

layout.xml

<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" >

    <com.example.customviewlayoutlesson.MyView
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />
</RelativeLayout>
MainActivity.java

package com.example.customviewlayoutlesson;

import android.os.Bundle;
import android.app.Activity;
import android.view.Menu;
import android.view.MenuItem;
import android.support.v4.app.NavUtils;

public class MainActivity extends Activity {

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

主要な処理は全てカスタムビュー側に書いてしまったので、Main側の処理はメインレイアウトxmlでカスタムビューを定義するだけになった。
このようにして、各部分のコンポーネント化を押し進めていくとコードの保守性・可読性が上がっていくだろう。