Inkscapeのグラデーション
グラデーションの編集方法が直感的にわかりづらかったので、メモしておきます。
グラデーションの種類
- Linear gradient(線状グラデーション)
- Radical gradient(放射状グラデーション)
色フェーズ
英語版では「stop」として扱われている概念。日本語版では「色フェーズ」と訳されているもよう。
Gradient editorのドロップダウンで選択できる。初期状態では2つ。「stop3757」とかなっている。
Inkscapeでは、グラデーションの起点となる色を「色フェーズ」と呼びます。 グラデーションを適用すると、 初期状態では単一色のフィルの色と、 その色の完全透明色の2つが色フェーズとなります。 つまり、フィルの色から無色に遷移するグラデーションとなります。
Inkscape@JP - リファレンス (オブジェクトの編集 >> グラデーション)
オフセット
オフセット0.00が起点、オフセット1.00が終点のような感じ。
初期状態ではオフセット0に不透明度255、 オフセット1に不透明度0で同じ色が設定されます。 線形グラデーションでは左端がオフセット0、 右端がオフセット1となっています。 円形グラデーションでは中心がオフセット0、円周がオフセット1です。
Inkscape@JP - リファレンス (オブジェクトの編集 >> グラデーション)
iPhoneのロック解除画面のようなボタンをAndroidで作成する
iPhoneのロック解除画面のようなボタンの作成を、Androidで試みました。
画像を用意する
Androidのデフォルトテーマのボタンは上下左右に隙間があるので、隙間が無いボタンの画像を用意しました。なお、これらの画像はpixelmatorの体験版で自作しました。
通常時
汎用性を持たせるため、全体を透過にしました。
押された時
こちらは透過にはなっていません。デフォルトにあわせてオレンジにしました。
StateListで状態を定義
作成した画像を、下記のようにボタンの状態に割り当てました。
res/drawable/btn_custom.xml
<?xml version="1.0" encoding="utf-8" ?> <selector xmlns:android="http://schemas.android.com/apk/res/android"> <item android:state_pressed="true" android:drawable="@drawable/btn_custom_normal_pressed" /> <item android:state_enabled="true" android:state_window_focused="true" android:drawable="@drawable/btn_custom_normal" /> </selector>
ボタンのスタイル定義
属性をまとめて指定できるよう、スタイルを"btn_custom"として定義しました。フォントが立体的に見えるように、shadow関連をちょっといじりました。
res/values/styles.xml
<?xml version="1.0" encoding="utf-8"?> <resources> <style name="btn_custom"> <item name="android:background">@drawable/btn_custom</item> <item name="android:textColor">#FFF</item> <item name="android:textSize">20dp</item> <item name="android:shadowColor">#000</item> <item name="android:shadowDx">-1.0</item> <item name="android:shadowDy">-1.0</item> <item name="android:shadowRadius">1.0</item> </style> </resources>
レイアウトの定義
LinearLayoutで9x9のボタンを並べ、それぞれにスタイルを適用しました。
res/layout/main.xml
<?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:background="#555" android:layout_height="fill_parent"> <TextView android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="@string/hello" /> <LinearLayout android:layout_height="wrap_content" android:id="@+id/linearLayout1" android:layout_width="match_parent"> <Button android:text="1" android:id="@+id/button1" style="@style/btn_custom" android:layout_width="wrap_content" android:layout_height="wrap_content"></Button> <Button android:text="2" android:id="@+id/button2" style="@style/btn_custom" android:layout_width="wrap_content" android:layout_height="wrap_content"></Button> <Button android:text="3" android:id="@+id/button3" style="@style/btn_custom" android:layout_width="wrap_content" android:layout_height="wrap_content"></Button> </LinearLayout> <LinearLayout android:layout_height="wrap_content" android:id="@+id/linearLayout2" android:layout_width="match_parent"> <Button android:text="4" android:id="@+id/button4" style="@style/btn_custom" android:layout_width="wrap_content" android:layout_height="wrap_content"></Button> <Button android:text="5" android:id="@+id/button5" style="@style/btn_custom" android:layout_width="wrap_content" android:layout_height="wrap_content"></Button> <Button android:text="6" android:id="@+id/button6" style="@style/btn_custom" android:layout_width="wrap_content" android:layout_height="wrap_content"></Button> </LinearLayout> <LinearLayout android:layout_height="wrap_content" android:id="@+id/linearLayout3" android:layout_width="match_parent"> <Button android:text="7" android:id="@+id/button7" style="@style/btn_custom" android:layout_width="wrap_content" android:layout_height="wrap_content"></Button> <Button android:text="8" android:id="@+id/button8" style="@style/btn_custom" android:layout_width="wrap_content" android:layout_height="wrap_content"></Button> <Button android:text="9" android:id="@+id/button9" style="@style/btn_custom" android:layout_width="wrap_content" android:layout_height="wrap_content"></Button> </LinearLayout> </LinearLayout>
備考
- ボタン9個にstyleを指定するのは面倒なので、実際にはthemeを使うべきだと思います。今回は、themeで指定しようとしたらなぜかpressedの画像がうまく適用されなかったので、styleでの指定にしました。もしかしたら、selectorの記述にどこかまずいところがあるのかもしれません。
- 画像のグラデーションが、伸縮で汚く見えてしまっています。9patchでの指定の仕方によっては改善できるのかも。あるいはgradientを使う?
btn_default.xmlの中身を調べる
ボタンのデザインをする場合のために、デフォルトのボタンがどのように実装されているか調べます。
デフォルトのボタンは、Widget.Buttonというstyleが適用されている。(たぶん)
Widget.Buttonスタイルの定義
android:backgroundにて、drawableのbtn_defaultが指定されている。
android.git.kernel.org Git - platform/frameworks/base.git/blob - core/res/res/values/styles.xml
173 <style name="Widget.Button"> 174 <item name="android:background">@android:drawable/btn_default</item> 175 <item name="android:focusable">true</item> 176 <item name="android:clickable">true</item> 177 <item name="android:textAppearance">?android:attr/textAppearanceSmallInverse</item> 178 <item name="android:textColor">@android:color/primary_text_light</item> 179 <item name="android:gravity">center_vertical|center_horizontal</item> 180 </style>
btn_default.xmlの中身
selectorで、Buttonの状態に応じた画像を指定している。
android.git.kernel.org Git - platform/frameworks/base.git/blob - core/res/res/drawable/btn_default.xml
17 <selector xmlns:android="http://schemas.android.com/apk/res/android"> 18 <item android:state_window_focused="false" android:state_enabled="true" 19 android:drawable="@drawable/btn_default_normal" /> 20 <item android:state_window_focused="false" android:state_enabled="false" 21 android:drawable="@drawable/btn_default_normal_disable" /> 22 <item android:state_pressed="true" 23 android:drawable="@drawable/btn_default_pressed" /> 24 <item android:state_focused="true" android:state_enabled="true" 25 android:drawable="@drawable/btn_default_selected" /> 26 <item android:state_enabled="true" 27 android:drawable="@drawable/btn_default_normal" /> 28 <item android:state_focused="true" 29 android:drawable="@drawable/btn_default_normal_disable_focused" /> 30 <item 31 android:drawable="@drawable/btn_default_normal_disable" /> 32 </selector>
色々組み合わせがあってややこしいので、表にしてみます。画像はAndroidのソースからの引用です。
line | state_ window_ focused |
state_ pressed |
state_ enabled |
state_ focused |
drawable | 9.png | 備考 |
---|---|---|---|---|---|---|---|
18-19 | false | なし | true | なし | btn_default_normal | 通常時 | |
20-21 | false | なし | false | なし | btn_default_normal_disable | disable時 | |
22-23 | なし | true | なし | なし | btn_default_pressed | 押された時 | |
24-25 | なし | なし | true | true | btn_default_selected | フォーカスがある時 | |
26-27 | なし | なし | true | なし | btn_default_normal | 通常時(18-19との違い不明) | |
28-29 | true | なし | なし | なし | btn_default_normal_disable_focused | disableでフォーカスあり | |
30-31 | なし | なし | なし | なし | btn_default_normal_disable | disable時(20-21との違い不明) |
ボタンの画像をちゃんと用意するなら、下記の5パターンの画像が必要ということみたいです。
・通常時
・disable時
・押されたとき
・フォーカスがあるとき
・disableでフォーカスがあるとき
LayoutPanelについて
Developer's Guide - Layout Using Panels - Google Web Toolkit - Google Code
GWT2.0からLayoutPanelというものが導入されて、そっちを使うといいみたいです。
LayoutPanelのサンプル
LayoutPanelにsetWidgetのバリエーションでWidgetを追加するサンプルです。ドキュメントのサンプルコードは端折られていて、そのままではコンパイルが通らないので、通る形にしています。
public class MainEntryPoint implements EntryPoint { @Override public void onModuleLoad() { // Widget sampleWidget = new SampleWidget("hogege"); Widget child0 = new Button("child0"); Widget child1 = new Button("child1"); Widget child2 = new Button("child2"); LayoutPanel p = new LayoutPanel(); p.add(child0); p.add(child1); p.add(child2); // Left panel p.setWidgetLeftWidth(child0, 0, Unit.PCT, 50, Unit.PCT); // Right panel p.setWidgetRightWidth(child1, 0, Unit.PCT, 50, Unit.PCT); // Center panel p.setWidgetLeftRight(child2, 5, Unit.EM, 5, Unit.EM); p.setWidgetTopBottom(child2, 5, Unit.EM, 5, Unit.EM); RootLayoutPanel rootLayoutPanel = RootLayoutPanel.get(); rootLayoutPanel.add(p); } }
child0を左から50%の横幅で指定、child1を右から50%の横幅で指定、
child2を上下左右で5EMの一に配置するコード。
同一プロセス内のServiceへのバインド
Serviceへのバインドには必ずAIDLが必要なのかと思ってたんですが、同一プロセス内であればAIDLは不要みたいです。勘違いしてました。
下記の記事を読んで知りました。
なお、このケースではサービスとクライアントが同じプロセスなので、AIDLを使わなくても問題ありません。
常駐アプリが作成できるAndroidの“サービス”とは (2/3) - @IT
Bound Services | Android Developers
Android DevelopersのBound Servicesでローカルなサービス(リモートでない)について記載されています。
アクティビティから、直接サービスのメソッドを呼び出せるか試します。
バインドされるサービス
onBindで、Binderの実装クラスを返します。接続した側が、Serviceの参照を触れるよう、Binderの実装クラスにServiceの参照を返すメソッドを持たせます。
public class SampleService extends Service { public class SampleServiceBinder extends Binder { SampleService getService() { return SampleService.this; } } @Override public IBinder onBind(Intent intent) { Log.v(TAG, "service onBind"); return new SampleServiceBinder(); } public String hoge() { return "hoge"; }
サービスにバインドするアクティビティ
bindServiceによりServiceConnection#onServiceConnected()が呼ばれ、パラメータにService#onBindの戻り値が渡されるので、キャストしてgetServiceからserviceのインスタンスを取得します。
public class SampleAppActivity extends Activity { private static final String TAG = "hoge"; protected SampleService mService; @Override public void onCreate(Bundle savedInstanceState) { Log.v(TAG, "activity onCreate"); super.onCreate(savedInstanceState); setContentView(R.layout.main); Button btn4 = (Button) findViewById(R.id.button4); btn4.setOnClickListener(new OnClickListener() { public void onClick(View v) { Log.v(TAG, "btn4 onClick"); Intent intent = new Intent(); intent.setClass(getApplicationContext(), SampleService.class); bindService(intent, conn , Service.BIND_AUTO_CREATE); } }); Button btn5 = (Button) findViewById(R.id.button5); btn5.setOnClickListener(new OnClickListener() { public void onClick(View v) { Log.v(TAG, "btn5 onClick"); if(mService != null) { String hoge = mService.hoge(); text1.setText(hoge); } } }); } private ServiceConnection conn = new ServiceConnection() { public void onServiceConnected(ComponentName name, IBinder binder) { Log.v(TAG, "onServiceConnected"); mService = ((SampleServiceBinder) binder).getService(); } public void onServiceDisconnected(ComponentName name) { Log.v(TAG, "onServiceDisConnected"); mService = null; } };
btn5をクリックするとSampleService#hoge()を呼び出せたことが確認できました。
UIBinderを使ってみる
GWTにはUIBinderというものがあり、これを使うとコンポーネントのコードとレイアウトを分離できるようです。そういうの好きなので試してみます。
Declarative Layout with UiBinder - Google Web Toolkit - Google Code
プラグインから作成可能
右クリックしてNew>UIBinderで作成できます。
GWT WidgetsとHTMLいずれかの選択ができました。
GWT Widetsの場合
com.google.gwt.user.client.uiをインポートして、g:なんたらとしていろいろタグを呼び出しています。
<!DOCTYPE ui:UiBinder SYSTEM "http://dl.google.com/gwt/DTD/xhtml.ent"> <ui:UiBinder xmlns:ui="urn:ui:com.google.gwt.uibinder" xmlns:g="urn:import:com.google.gwt.user.client.ui"> <ui:style> .important { font-weight: bold; } </ui:style> <g:HTMLPanel> Hello, <g:Button styleName="{style.important}" ui:field="button" /> </g:HTMLPanel> </ui:UiBinder>
HTMLの場合
こちらは、見慣れたhtmlタグです。デザイナーと分業するようなときはこっちが良いのかも。
<!DOCTYPE ui:UiBinder SYSTEM "http://dl.google.com/gwt/DTD/xhtml.ent"> <ui:UiBinder xmlns:ui="urn:ui:com.google.gwt.uibinder"> <ui:style> .important { font-weight: bold; } </ui:style> <div> Hello, <span class="{style.important}" ui:field="nameSpan" /> </div> </ui:UiBinder>
いずれの場合も、ui:fieldという属性を定義すると、それをjavaファイルから@UIFieldというアノテーションで参照できるようです。
GWTでRemoteServiceによる非同期HTTP通信を組み立てる
generate sample codeを使わずGWTプロジェクトを組み立ててみる - 理系のためのTIPS集
前回に続いて非同期HTTP通信の実装を試します。
ボタンをクリックしたら/mymodule/hogeにリクエストを投げて、結果をalertさせてみます。
GWTにおけるサーバとの通信方法
RPC(Remote Procedure Calls)という仕組みを利用します。
Making Remote Procedure Calls - Google Web Toolkit - Google Code
参考:通常のサーブレットの場合
RPCを試す前に、通常のサーブレットの場合を確認してみます。
com.naokichick.samplegwt.server.MyServletを作成し、/mymodule/hogeにマッピング。
<?xml version="1.0" encoding="utf-8"?> <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" version="2.5"> <servlet> <servlet-name>MyServlet</servlet-name> <servlet-class>com.naokichick.samplegwt.server.MyServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>MyServlet</servlet-name> <url-pattern>/mymodule/hoge</url-pattern> </servlet-mapping> <!-- TODO: Optionally add a <welcome-file-list> tag to display a welcome file. --> </web-app>
package com.naokichick.samplegwt.server; import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @SuppressWarnings("serial") public class MyServlet extends HttpServlet { @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { resp.setContentType("text/plain"); resp.getWriter().println("hoge"); resp.flushBuffer(); } }
$.ajax({ url: "./mymodule/hoge", type: "get", success: function(data){ alert(data); }, error: function(){ alert("error"); } });
上記を踏まえ、RPCを利用したajax通信を書いてみます。
RemoteServiceServletは普通のリクエストを受け付けない
試しにRemoteServiceServletを通常のServletのようにマッピングして、リクエストを試みます。
[WARN] Exception while dispatching incoming RPC call
javax.servlet.ServletException: Content-Type was 'application/x-www-form-urlencoded'. Expected 'text/x-gwt-rpc'.
フォームからリクエストを投げてみると、上記エラーとなりました。
リクエストのURIは何によって決まるか?
リクエストのURIは、EntryPointのModuleNameとRemoteServiceの@RemoteServieRelativePathで決まるようです。
<module rename-to="mymodule"> <inherits name="com.google.gwt.user.User" /> <source path="client" /> <entry-point class="com.naokichick.samplegwt.client.MyEntryPoint" /> </module>
上記の定義で、モジュールの名前をmymoduleとリネームし、MyEntryPointをエントリポイントに指定しています。リネームしているのはデフォルトが完全修飾名だからです。
エントリポイント内からMyRemoteServiceのメソッドを呼び出します。MyRemoteServiceのURIは@RemoteServiceRelativePathの定義により、hogeになります。
@RemoteServiceRelativePath("hoge") public interface MyRemoteService extends RemoteService { public String getResult(String param); }
これで、MyEntryPointからMyRemoteServiceに対するメソッド呼び出しは"mymodule/hoge"になりました。あとは、このURIのマッピングを処理するRemoteServiceServletに実装します。
@SuppressWarnings("serial") public class MyRemoteServiceImpl extends RemoteServiceServlet implements MyRemoteService { @Override public String getResult(String param) { return "param is " + param; } }
mymoduleから/hogeに対する非同期リクエストとハンドリングの実装。パラメータは"fuga"。
public void onClick(ClickEvent event) { Window.alert(GWT.getModuleName()); MyRemoteServiceAsync service = GWT.create(MyRemoteService.class); service.getResult("fuga", new AsyncCallback<String>() { @Override public void onSuccess(String result) { Window.alert(result); } @Override public void onFailure(Throwable caught) { Window.alert("failure"); } }); }
上記を実行すると、/mymodule/hogeにマッピングされたサーブレットへリクエストが行われ、その結果を受け取れることが確認できました。