woshidan's blog

そんなことよりコードにダイブ。

ShapeDrawableのRoundRectShapeとGradientDrawableをそれぞれ使って角丸ボタン作ってみた

ShapeDrawableのRoundRectShape、あるいは、GradientDrawableを使うとコードからフチの余白や色を指定できる形で角丸四角形の背景を持つボタンが作ることができます。

もともとShapeDrawableで書いていたのですが、こちらの記事でShapeDrawableのリソースをGradientDrawableコンパイルされていることを知り、GradientDrawableで書いてみたらスッキリ記述できたので書き直した、という話です。

どちらにせよ、リソースファイルから読み込んだほうが基本的に高速なので、あまり表示頻度が多くない箇所をカスタマイズして表示したいとか、そういった場合に使えば良いと思います。

ShapeDrawableのRoundRectShapeで作る場合

同じ指定をxmlで記述した場合

今回はxmlで記述した場合は下記のようになる指定をコードで実装することにします。

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
    <padding android:left="@dimen/button_padding_horizontal" />
    <padding android:top="@dimen/button_padding_vertical" />
    <padding android:right="@dimen/button_padding_horizontal" />
    <padding android:bottom="@dimen/button_padding_vertical" />
    <!-- 上記の指定はButton要素に下記属性を追加するのと同じ結果となる。
      android:paddingLeft="@dimen/button_padding_horizontal"
      android:paddingTop="@dimen/button_padding_vertical"
      android:paddingRight="@dimen/button_padding_horizontal"
      android:paddingBottom="@dimen/button_padding_vertical"  -->
    <!-- デフォルトのボタンではButton要素は
      paddingを設定しなくてもButton要素に余白を持っているが、それは
      android.R.drawable.btn_default_normal.xmlなどで
      paddingが指定されているため
       -->
    <solid android:color="#f00"/>
    <corners android:radius="@dimen/button_round_radius"/>
</shape>

RoundRectShapeを利用してコードで記述した場合

        Button button = (Button) findViewById(R.id.button_bg_code);

        // 角丸の設定
        int roundRadius = getResources().getDimensionPixelSize(R.dimen.button_round_radius);
        ShapeDrawable shape = new ShapeDrawable();
        shape.setShape(new RoundRectShape(new float[] {roundRadius, roundRadius, roundRadius, roundRadius, roundRadius, roundRadius, roundRadius, roundRadius},
                null,     // 背景のうち、真ん中の方をくりぬいた形にすることができるのですが、その領域の大きさをRectFで指定可能. 利用しない(=一色塗りつぶしにする)場合はnull
                null));   // 背景のうち、くりぬいた部分の長方形の角丸指定. 利用しない(=くりぬいた部分は長方形のまま)場合はnull

        // 余白の設定
        int horizontalPadding = getResources().getDimensionPixelSize(R.dimen.button_padding_horizontal);
        int verticalPadding = getResources().getDimensionPixelSize(R.dimen.button_padding_vertical);
        shape.setPadding(horizontalPadding, verticalPadding, horizontalPadding, verticalPadding);
        shape.getPaint().setColor(Color.RED);

        if (BuildConfig.VERSION_CODE >= 16) {
            button.setBackground(shape);
        } else {
            button.setBackgroundDrawable(shape);
        }
<!-- レイアウト -->
    <Button
        android:id="@+id/button_bg_resource"
        android:layout_below="@id/some_element"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="@drawable/test"
        android:layout_marginTop="8dp" <!-- 見やすくするためつけてる -->
        android:text="TestTestTestTest" />

    <Button
        android:id="@+id/button_bg_code"
        android:layout_below="@id/button_bg_resource"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="8dp" <!-- 見やすくするためつけてる -->
        android:text="TestTestTestTest" />
<!-- dimenリソースの設定 -->
<resources>
    <dimen name="button_round_radius">8dp</dimen>

    <!-- この辺の元ネタはこちら https://github.com/android/platform_frameworks_base/blob/c29fff50322599f53feadf9cf87df9956c9ac44e/core/res/res/values/dimens_material.xml#L109-L116 -->
    <dimen name="button_padding_horizontal">8dp</dimen>
    <dimen name="button_padding_vertical">4dp</dimen>
</resources>

見た目はこんな感じです。上がdrawableリソースで背景を指定したボタンで下がコードで背景を指定したボタンになります。

f:id:woshidan:20170509141454p:plain

GradientDrawableで作る場合

コードでの記述

        Button button = (Button) findViewById(R.id.button_bg_code);

        // 角丸の設定
        int roundRadius = getResources().getDimensionPixelSize(R.dimen.button_round_radius);

        GradientDrawable gradientDrawable = new GradientDrawable();
        gradientDrawable.setCornerRadius(roundRadius);
        gradientDrawable.setColor(Color.RED);

        // 余白の設定はButton要素の属性でやる

        if (BuildConfig.VERSION_CODE >= 16) {
            button.setBackground(gradientDrawable);
        } else {
            button.setBackgroundDrawable(gradientDrawable);
        }
<!-- レイアウト -->
    <Button
        android:id="@+id/button_bg_resource"
        android:layout_below="@id/some_element"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="@drawable/test"
        android:layout_marginTop="8dp"  <!-- 見やすくするためつけてる -->
        android:text="TestTestTestTest" />

    <Button
        android:id="@+id/button_bg_code"
        android:layout_below="@id/button_bg_resource"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:paddingLeft="@dimen/button_padding_horizontal"
        <!-- GradientDrawableの場合はpaddingを設定するメソッドはないので、
        View側にpaddingの指定を置く -->
        android:paddingTop="@dimen/button_padding_vertical"
        android:paddingRight="@dimen/button_padding_horizontal"
        android:paddingBottom="@dimen/button_padding_vertical"
        android:layout_marginTop="8dp" <!-- 見やすくするためつけてる -->
        android:text="TestTestTestTest" />

見た目は変わってません。

参考

AndroidのNDKやABIについてのメモ

はじめに

C系の言語で動いているクロスプラットフォームのブリッジを作成するには、そのコンパイルなどの過程でNDKに関する知識があるとトラブルシューティングやテストケースの設計に役立つことがあります。

なので、ここではその概要をまとめておきます。はじめに用語からまとめようと思ったのですが、

https://developer.android.com/ndk/guides/index.html?hl=ja

Native Development Kit(NDK)は、Android アプリで C および C++ コードを使用できるようにするツールのセットのことです。NDK を使用して独自のソースコードをビルドしたり、事前にビルドされた既存のライブラリを活用したりできます。

https://developer.android.com/ndk/guides/concepts.html?hl=ja

アプリケーション バイナリ インターフェース(ABI)はアプリのマシンコードが実行時にどのようにシステムと連携するかを正確に定義したものです。 NDK はこれらの定義に対して .so ファイルをビルドします。

と言われてもよくわからなかったので、NDKがABIを使ってC, C++ソースコードから.soファイルをビルドする周辺で何をしているかを説明します。

.soファイルとは

と書いたものの、書いた本人は.soファイルについての事前知識が調査開始時点でなかったため、簡単に説明します。

soshared object fileの略です。

.soファイルは、UNIX系の環境下での共通ライブラリファイルのことで、Windowsでいうdllにあたります。具体的には、複数の実行ファイル間で共有して利用される処理の定義がまとめられています。

中身は実行形式ファイルですが、あくまで処理を含んでいるだけでエントリポイントがなく単体で起動することはできません。

各々の実行ファイルに含まれず、実行ファイルを起動した時に動的にロードとリンクが行われるため、動的(ダイナミック)リンクライブラリと呼ばれることがあり、Android Developers GuideのNDKについての記述では主に動的リンクライブラリ、と呼称されています。

Androidでは、アプリのコンパイル時にリンクされている方が.a(静的リンクライブラリ), アプリの起動時にロードされてリンクされる方が.soで、NDKを使ってABIに従って作成される方が基本的に.soという感じです。

自分が作成したライブラリが.so か、.aになるかはコンパイルの設定によりますが、Androidアプリのビルドの文脈だと、.soファイルの方しか、公式のドキュメントに記載がなかったので、こちらが多いかもしれません。

NDKを利用してビルドする際の流れ

https://developer.android.com/ndk/guides/concepts.html のフローをもとに適宜用語の説明を入れながら説明します。

  1. Androidプロジェクトを作成します
  2. Androidプロジェクト直下にjniディレクトリを作成し、ネイティブライブラリ(.aファイル, .soファイルなど.アーキテクチャごとにフォルダを用意したり…)とコンパイル対象のソースコード(CやC言語で書かれたソースコード)とそれらをコンパイルするモジュールにどう含むか記述していくAndroid.mkを配置
  3. 2のディレクトリに任意でターゲットABI, ツールチェーン, リリース/デバッグモード, STLを設定するApplication.mkファイルを作成(任意)
  4. デフォルト設定
  5. ABI: armeabi
    • ABIというのは、ネイティブライブラリをコンパイルする際、コンパイルした.so.aファイルがインストール対象の端末で動くことを保証するための規約みたいなものです。
  6. ツールチェーン: GCC 4.8(4.8 32bit / 4.9 64bit端末対応らしい)
  7. モード: リリース
    • リリースモードだと変数宣言が最適化されていたりして、C言語側のステップ実行が困難になったりします
  8. STL: system
  9. NDKツールを適切に利用するために作成されたシェルのラッパーであるndk-buildを使用して、Cのソースコードをネイティブ(.so, .a. 主に.so?)ライブラリへコンパイルしたり、静的ライブラリをリンクしたり
  10. Javaソースコードをビルドして、DVMで実行可能な.dexファイルを生成
  11. アプリの実行に必要な .so, .dex、その他ファイルをすべてapkファイルにパッケージ化

ところで

.soファイルやネイティブライブラリと.dexを一緒に処理していませんが、JNI用のヘッダとそれに対応した規則のメソッド名で作成していけば、JVM(DVM)上でメソッドテーブルが作成されると理解していて、それにしたがってJava<=>C,C++間でメソッドを利用し合うことが可能みたいです。

この辺を調べていると、CMakeといった言葉もでてきますが、ひとまずこの記事を書くにあたっての状況に限っては(Andorid Studioでビルドをしない場合は)関係なかったので省きます。

CMakeについては、また今度機会あればまとめます。

参考

https://developer.android.com/ndk/guides/concepts.html http://blog.katty.in/4347 http://www.peliphilo.net/archives/681 http://qiita.com/m1takahashi/items/3a3c9d2845e9b57aeda3 http://morado106.blog106.fc2.com/blog-entry-80.html

COUNT関数の引数にNULLが入る場合について

SQLで行数を数える時に利用する COUNT 関数で一番使い慣れているのは COUNT(*) なのですが、 COUNT(col) や、COUNT(1かNULLになる式)といった形を見かけたのでメモ。

見かけた時は面白いなと思ったのに短い。。

iOSのProvisioning Profile周りについてざっくり確認するシートを作りました

iOSのProvisioning Profile周りについて毎回ハマっているような気がしていたので、どのファイルをいつ用意して用意した後どうすればいいのか、といったことを手短に確認するために

  • 主になんて呼ばれているか
  • 代表的なファイル名
  • 主にいつ作成するか、どう作成するか
  • ファイルをDLするか、DLした後どうするか
  • XCode上でそのファイルに関連してどう設定するか
  • そのほか備考

をまとめて、作成する際に必要、など関連のあるところの線を引いた図を作ったので貼ります。

f:id:woshidan:20170331174938p:plain

上の図に千鳥足みたいなのありますが、一つのファイルに対して他方のファイルが複数存在するといった場合は複数存在する方が雑に鳥足みたいにしました。 なんかそういうツールや様式があった気がしますが、それは次の機会ということで。

参考

https://i-app-tec.com/ios/provisioning-profile.html http://macdays.hatenablog.com/entry/2013/10/11/172532 http://qiita.com/edo_m18/items/6f10e57f95b25d9dab4e http://qiita.com/fujisan3/items/d037e3c40a0acc46f618

注釈

※1 Apple Developer Centerに登録した1つのチームから複数の開発者を招待して、その開発者はそれ以外のチームに所属していないという想定。個人開発している人もいるのでいつも事実では無いですが会社アドレスのアカウントでは事実なこと多いのでは
※2 証明書周りがよくわからなくなってむやみやたらに再発行され各種ファイルの作り直しに励むのはよくあるので助けてください
※3 1つのアプリについてProductionとSand Boxというつもり。

気になるところあったらツッコミください。

ObjCで戻り値以外にNSErrorのポインタを出しているのが面白かった話

NSErrorの使い方というよりはNSErrorが使われる場面についての話で、 C言語をはじめとした関数やメソッドの戻り値は基本的に1つになります。

複数の値を返したい場合、構造体を作ったり、オブジェクトを使ったり、といった方法がありますが、基本的にメソッドの戻り値は1つの値か、関連する値をまとめた一つのオブジェクト、となります。

普段はそこまでこまらないのですが、本来の処理と全然関係ない情報を呼び出し元に返したくなった場合はどうしたらよいでしょうか。例えば、エラーが起きてしまって本来の値が返せない、といった場合。

JavaRubyなら上記のような想定外の事態が起きて処理が進められない場合には例外を投げてしまえばいいのでしょうが、メモリ管理にARCを利用している関係もあり、Objective-Cでは基本的にアプリがそのまま死ぬような場合以外は例外を投げたくありません。

では、どうするか、と言いますと、例外を表すNSErrorのインスタンスのポインタを渡して、何か起きた時にはNSErrorのポインタがさす変数にnil以外が入っているようにするのです。

// 引用元: http://www.inazumatv.com/contents/archives/9078
NSError *error = nil;
NSString *result = [ExampleError exampleMethod:&error]
 
if (error) {
    // error処理
}

// class ExampleError
+(NSString *)exampleMethod:(NSError **)didFailWithError
{
    NSError *error = nil;
    NSString *resultMessage = @"success message";
 
    [self innerMethod:&error];
    if (error) {
        *didFailWithError = error;
        return nil;
    }
 
    return resultMessage;
}

というのが面白かった、というお話。

参考:

iOSでファイルにオブジェクトの状態を保存する/.plistとは

ObjCのコードを読んでいた時に書いたメモを放流。

アーカイブとは

プログラムで使われている複数のオブジェクトを、その属性値や相互の関係も含めてファイルに保存したり、他のプロセスに渡したりしたい場合があります。 そのために、互いに関連するオブジェクトをバイト列に変換する機能をアーカイブといい、さらにバイト列から元のオブジェクトを復元する機能をアンアーカイブといいます。 また、オブジェクトを変換して得られたバイト列をアーカイブと呼ぶことも。

一方、シリアライゼーションはプロパティーリスト程度の単純な階層構造しか表現できません。

アーカイブができるオブジェクトは、NSCodingプロトコルに適合するように実装する必要があります。

// アーカイブ用
- (void)encodeWithCoder:(NSCoder *)coder
{
  [super encodeWithCoder:coder];
  // スーパークラスがNSCodingプロトコルに適合していない場合は不要

  [coder encodeObject:オブジェクト forKey:キー文字列];
  // またはencodedConditionalObject:forKey:を使う

  ...
  [coder encodeDouble:実数の変数 forKey:キー文字列];
  // いくつかの方に合わせたメソッドが用意されている
}
// オブジェクトがまた依存しているオブジェクト...とたどっていくと元のオブジェクトのencode...が
// 呼ばれそうだが、すでにアーカイブされていたら二度目はされないらしい
// アンアーカイブ用
- (id)initWithCoder:(NSCoder *)coder
{
  self = [super initWithCoder:coder];
  // スーパークラスがNSCodingプロトコルに適合していない場合は[super init]でよい

  変数 = [[coder decodeObjectForKey:キー文字列] retain];

  ...
  変数 = [coder decodeDouble:キー文字列];
}

NSKeyedArchiver

上記のencodeWithCoderを使って、対象となるオブジェクトをエンコードした結果をデータオブジェクトに書き込み。 初期化の際は、書き込み先のNSMutableDataのオブジェクトが必要となります。

一つずつアーカイブしていっても良いが、ルートオブジェクトをエンコードすると再起的にオブジェクトグラフがアーカイブ化される。全部完了したらfinishEncodingを読んで後処理をします。

書き込み先はデータオブジェクトなのでデータオブジェクトからファイルへ書き込んだり、他プロセスへ送信したりができるし、ファイルに書き込むまで一括で出来るメソッドもあります。

NSKeyedUnarchiver

上記のinitWithCoderを使って、アーカイブされたオブジェクトが書き込まれたデータオブジェクトからオブジェクトを復元します。 ファイルからデータオブジェクトを作って読み込んだりもできます。

.plist

https://developer.apple.com/library/content/documentation/General/Reference/InfoPlistKeyReference/Articles/AboutInformationPropertyListFiles.html http://glassonion.hatenablog.com/entry/20110910/1315609950 http://qiita.com/hp0me/items/b619680611fd6667273f

Apple系が発祥のNSPropertyListSerializationクラスでパースできる*1、データ永続化などのために使われるファイル形式。 プロパティリストと呼ぶみたいです。

代表的なものはアプリの設定を保存している Info.plist だが、それ以外にも利用可能なようです。 ファイル形式としてはXMLの一種だと思います。*2

*1:NSArray や NSDictionary クラスでもまぁできる

*2:そういえば、AndoridのSharedPreferencesの実装もXMLだったはず http://qiita.com/ochim/items/dc4d77d478e87c1449ad

ideviceinstallerで「Could not connect to lockdownd. Exiting.」と出てアプリのインストールに失敗する

$ ideviceinstaller -u b93fd1bed1bbdf952070fa4160a34510efbe71ee -i /Users/woshidan/to/app/dir/iOSApp/build/sym/Release-iphoneos/iOSApp.app
Could not connect to lockdownd. Exiting.

github.com

Mac OS X El Captain以降のバージョンだと、iOSアプリのインストールに利用している ideviceinstaller が依存している libimobiledevice のバージョンが古い場合、iPhoneにアプリをインストールする際に操作する必要のある /var/db/lockdown の編集権限がない場合があります。

この場合は、libmobiledeviceから新しく入れ直してした後、古い /var/db/lockdownディレクトリ以下を削除してやり直すと自分の場合は解決しました。

brew uninstall ideviceinstaller -g
brew uninstall libimobiledevice -g
brew install --HEAD libimobiledevice -g // libimobiledeviceの新しいバージョンを入れ直す
brew install ideviceinstaller -g
sudo rm -rf /var/db/lockdown/*