SQLの窓

2013年02月02日


Android : 画面定義で、Incorrect line ending: found carriage return (\r) without corresponding newline (\n)

原因は特定できていませんが、android - Incorrect line ending: found carriage return (\r) without corresponding newline (\n) - Stack Overflow で書かれている対処方法の

CTRL+SHIFT+F
( ソースを整形 ) 

を実行して保存すると、エラーが無くなりました。後は、通常に実行可能でした。

※ 出先の PC でフォルダごと保存したものを、自宅でインポートして発生しています。


posted by lightbox at 2013-02-02 22:21 | Android | このブログの読者になる | 更新情報をチェックする

2012年11月28日


Android(4.0.3) : WebView を使って Google にログインしてアクセストークンを取得する

ログイン画面は Google が提供する画面が表示されます。



そこでログインすると、登録済のアプリケーションの名前が表示されて、処理を許可するかどうかの確認画面が表示されて、許可すると登録済のリダイレクト URL が表示されます。ここで使用している Google API に登録したアプリケーションは、『Client ID for web applications』として、自分の API Console で作成や表示が可能です。

※ Calendar API と Google+ API を ON にしています。

クリップボードアクセスについて

エミュレータでログインする場合、ユーザーの入力を省略する為に、アプリケーションでユーザ文字列をクリップボードにセットしています。その際、ClipboardManager を使うわけですが、android.content.ClipboardManager を使っています。もうひとつ、android.text.ClipboardManager がありますが、これは将来的に廃止するので android.content.ClipboardManager を使うようにと指示されます。これは、Eclipse でのチェック機能でかなり明示されますし、android.content.ClipboardManager を使っても @SuppressLint("NewApi") を記述するようにガイドされました。

WebView の JavaScript 設定

Google 側の処理で、JavaScript が必要になります。もし、JavaScript が使用できない場合はメッセージが表示されて先へ進まなくなりますので、webView.getSettings().setJavaScriptEnabled(true); を実行してからログイン画面を表示しています。

アクセストークンを POST で取得

WevView のアドレスに『仮のアクセストークン』が設定されるので、そこから取り出して正式なアクセストークンを取得する為に『Client secret』をさらにセットして呼び出します。その結果が JSON なので、その中の access_token を GSON で取得するので、プロジェクトの lib フォルダには、GSON の jar を保存しておく必要があります。

Web に対するアクセスの為の AsyncTask

Android 4.0.3 でインターネットにアクセスしようとすると、たとえ SDK のメソッドであっても同期処理の場合は非同期処理として記述しなおす必要があります。その際のむ最も簡単な方法として、無名の AsyncTask のインスタンスを使用しています。

UI 処理は onPostExecute で

doInBackground は、別スレッドなので画面にアクセスはできません。UI スレッドである onPostExecute に必要なデータを return して処理する必要があります。( ジェネリック型の型パラメータの最後のパラメータの型で受け渡しの型を決定します )

※ 『ジェネリック型の型パラメータ』は Microsoft の C# の表現です / <String, Void, String>

※ 最初の型パラメータは doInBackground に対するもので、配列になります
※ 真ん中のパラメータは、doInBackground から publishProgress で投げた内容を配列で受け取ります
※ ここでは使用していないので Void にしています。

AsyncTask | Android Developers
// ****************************************************************
// アクティビティの初期処理
// ****************************************************************
@SuppressLint("NewApi")
@Override
public void onCreate(Bundle savedInstanceState) {
	super.onCreate(savedInstanceState);
	setContentView(R.layout.activity_main);

	// ************************************************************
	// WebView のイベントを処理する
	// ************************************************************
	WebView webView = (WebView)findViewById(R.id.webView1);
	webView.setWebViewClient( new WebViewClient() {
			// ページがユーザに対して表示された時のイベント
			public void onPageFinished (WebView view, String url) {
				Log.i("onPageFinished:", url);
			}
			
			public boolean shouldOverrideUrlLoading(WebView view, String url) {
				Log.i("shouldOverrideUrlLoading:", url);
				if (url.indexOf("https://plus.google.com/u/0/101280392108947207617/posts") == 0) {
					int cur = url.indexOf("=");
					String code = url.substring(cur + 1);
					Log.i("onPageFinished:", code);

					(new AsyncTask<String, Void, String>() {
						@Override
						protected String doInBackground(String... args) {
							String json_string = "";

							try {
								URL url = new URL("https://accounts.google.com/o/oauth2/token");
								// 接続オブジェクト
								HttpURLConnection http = (HttpURLConnection)url.openConnection();
								http.setConnectTimeout(30000);
								http.setReadTimeout(30000);
								http.setDoInput(true);
								http.setRequestMethod("POST");

								// ****************************************************
								// 送信部分
								// ****************************************************
								OutputStreamWriter osw =
									new OutputStreamWriter(http.getOutputStream());
								BufferedWriter bw = new BufferedWriter(osw);
								
								// 送信データ部分
								String client_id = "";
								String client_secret = "";
								String redirect_uri = "https://plus.google.com/u/0/101280392108947207617/posts";
								
								String body = "";
								body = "code=" + args[0];
								body += "&grant_type=authorization_code";
								body += "&redirect_uri=" + redirect_uri;
								body += "&client_id=" + client_id;
								body += "&client_secret=" + client_secret;
								bw.write( body );
		
								bw.close();
								osw.close();
										
								// ****************************************************
								// 受信部分
								// ****************************************************
								// UTF-8 でリーダーを作成
								InputStreamReader isr = new InputStreamReader(http.getInputStream(), "UTF-8");
								
								// 行単位で読み込む為の準備   
								BufferedReader br = new BufferedReader(isr);   
								String line_buffer;   
								// BufferedReader は、readLine が null を返すと読み込み終了   
								while ( null != (line_buffer = br.readLine() ) ) {   
									// コマンドプロンプトに表示   
									json_string += line_buffer;
								}
					 
								br.close();
								isr.close();
		
								http.disconnect();
							}
							catch (Exception e) {
								e.printStackTrace();
							}
							
							// アクセストークン
							return json_string;
						}
						@Override
						protected void onPostExecute(String result) {

							// パーサーを取得
							JsonParser jp = new JsonParser();
							// 文字列をパース
							JsonElement je = jp.parse(result);

							// key と value を取得する為に、JsonObject から、entrySet メソッドを実行
							Set<Map.Entry<String, JsonElement>> entrySet = je.getAsJsonObject().entrySet();

							// イテレータを取得
							Iterator<Map.Entry<String, JsonElement>> it = entrySet.iterator();

							// 一覧表示
							while(it.hasNext())
							{
								Map.Entry<String, JsonElement> entry = it.next();
								
								String key = entry.getKey();
								JsonElement value = entry.getValue();
								
								if ( key.equals("access_token") ) {
									EditText editText = (EditText)findViewById(R.id.editText1);
									editText.setText(value.getAsString());
									break;
								}
							}			
						}
					}).execute(code);
					
				}
				// true を指定すると、リダイレクト処理をしなくなる
				// false でリダイレクトの場合、ここが二度よばれてから onPageFinished が呼ばれる
			    return false;
			}
		}
	);		

	String client_id = "";
	String loginUrl = "https://accounts.google.com/o/oauth2/auth";
	String type = "code";
	String redirect_uri = "https://plus.google.com/u/0/101280392108947207617/posts";
	String scope = "https://www.googleapis.com/auth/calendar+https://www.googleapis.com/auth/calendar.readonly+https://www.googleapis.com/auth/plus.me";

	String access_url = "";

	access_url = loginUrl;
	access_url += "?response_type=" + type;
	access_url += "&client_id=" + client_id;
	access_url += "&redirect_uri=" + redirect_uri;
	access_url += "&scope=" + scope;

	// ログイン画面
	webView.getSettings().setJavaScriptEnabled(true);
	webView.loadUrl(access_url);
	
	ClipboardManager clipboardManager = (ClipboardManager)this.getSystemService(CLIPBOARD_SERVICE);
	
	//クリップボードに格納するItemを作成
	ClipData.Item item = new ClipData.Item("ユーザID");

	// データの種類
	String[] mimeType = new String[1];
	mimeType[0] = ClipDescription.MIMETYPE_TEXT_PLAIN;
	 
	//クリップボードに格納するClipDataオブジェクトの作成
	ClipData cd = new ClipData(new ClipDescription("text_data", mimeType), item);
	
	clipboardManager.setPrimaryClip(cd);

}


タグ:android java
posted by lightbox at 2012-11-28 17:14 | Android | このブログの読者になる | 更新情報をチェックする

2012年11月21日


Eclipse+Android(4.0.3) のメニューの処理

※ Android の最新(2012年6月末)の開発環境に関して

SkyDrive へ移動


普通にプロジェクトを作成すると以下のようになります。メニューの XML は最初から作成されていて、メニューが作成される最初のイベント(onCreateOptionsMenu)も作成されていますので、onOptionsItemSelected を追加して、メニューの処理を実装します。

▼ ifRoom フラグの意味
それのための余地がある場合にのみ、アクションバーでこの項目を配置します。



※ この設定により、以降のメニュー位置になります


Google、AndroidのMenuボタンに“告別
Googleは既存アプリのMenuボタンが担っていた機能を、「ActionBar」クラスを使って組み込み直すよう勧めている。
package com.example.androidapp;

import android.os.Bundle;
import android.app.Activity;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.EditText;
import android.widget.Toast;

public class MainActivity extends Activity {

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

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.activity_main, menu);
        return true;
    }
    
    @Override
	public boolean onOptionsItemSelected(MenuItem item) {
		
		// 選択されたメニュー項目の ID
		int menuId = item.getItemId();
		// 選択されたメニュー項目に表示されているテキスト
		String menuText = item.getTitle().toString();
		// 画面上のテキストフィールド
		EditText editText = (EditText)findViewById(R.id.editText1);
		// 画面上画像表示用コントロール
		ImageView imageView = (ImageView)findViewById(R.id.imageView1);
		
		switch(menuId){
		case R.id.menu_action1:
			Log.i("DEBUG:", "処理1が選択されました");
			// 実際の番号でアイコンリソースを指定
			imageView.setImageDrawable(getResources().getDrawable(17301555));
			
			break;
		case R.id.menu_action2:
			Log.i("DEBUG:", "処理2が選択されました");
			// 定義IDでアイコンリソースを指定
			imageView.setImageDrawable(getResources().getDrawable(android.R.drawable.ic_menu_add));
			break;
			
		case R.id.menu_post:
			// 画面遷移用
			Log.i("DEBUG:", "投稿画面へ移動します");
			break;
		}

		// 選択されたメニュー項目に表示されているテキストを
		// テキストフィールドに表示
		editText.setText(menuText);

		return true;
	}	
}

ここでは、『投稿』で画面遷移を想定しています。17301555 は、android.R.drawable.ic_menu_add実際の値です。



画面定義
<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" >

    <TextView
        android:id="@+id/textView1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentLeft="true"
        android:layout_alignParentRight="true"
        android:layout_alignParentTop="true"
        android:padding="20px"
        android:text="CTRL+F12でエミュレータを横にすると、メニューにテキストが付加されます" />

    <EditText
        android:id="@+id/editText1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentLeft="true"
        android:layout_alignParentRight="true"
        android:layout_below="@+id/textView1"
        android:ems="10" />

    <ImageView
        android:id="@+id/imageView1"
        android:layout_width="48px"
        android:layout_height="48px"
        android:layout_below="@+id/editText1"
        android:layout_marginTop="24dp"
        android:src="@drawable/ic_action_search" />

</RelativeLayout>



メニュー項目定義
<menu xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:id="@+id/menu_post"
          android:icon="@android:drawable/ic_menu_save"
          android:title="投稿"
          android:showAsAction="ifRoom|withText" />
    <item android:id="@+id/menu_action1"
        android:title="処理1"
        android:orderInCategory="100"
        android:showAsAction="never" />
    <item android:id="@+id/menu_action2"
        android:title="処理2"
        android:orderInCategory="100"
        android:showAsAction="never" />
</menu>

android:drawable の android は、package を意味しています。ic_menu_save 以外もたくさん定義されています。

関連する Android ドキュメント

Action Bar

関連する記事

android-binding を使って Windows C#(XAML) のようなバインド処理の実装



posted by lightbox at 2012-11-21 23:48 | Android | このブログの読者になる | 更新情報をチェックする

2012年09月21日


android-binding を使用した、固定値、イベント、オプションメニューの構築


Google ドライブからダウンロード



バインドの方法としては、BindingActivityV30 + inflateAndBind を使うのが最新なのですが、作者さんの最新のサンプルがまだそれを使って無いのでここでは利用していません。それを利用すると、Android の最新バージョンで ActionBar を使う事を簡単に変更できるようです( メイン画面とメニューと ActionBar を一つの XML で定義します / 動作確認はしました )

プロジェクト作成の基本部分

android-binding を使って Windows C#(XAML) のようなバインド処理の実装

画面定義
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:binding="http://www.gueei.com/android-binding/"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerHorizontal="true"
        android:layout_centerVertical="true"
        android:padding="@dimen/padding_medium"
        binding:text="hello"
        tools:context=".P1Activity" />

    <Button
        android:id="@+id/button1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentLeft="true"
        android:layout_alignParentRight="true"
        android:layout_alignParentTop="true"
        binding:text="button_value" 
        binding:onClick="Button_Click_1"/>

</RelativeLayout>



メニュー定義
<menu xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:binding="http://www.gueei.com/android-binding/">
    <item android:id="@+id/menu_settings"
        binding:title="bind_menu"
        android:orderInCategory="100"
        android:showAsAction="never" 
        binding:onClick="Menu_Click_1"/>
</menu>



BindingActivity

bindModel.button_value.set("ボタン"); は、クラス内で初期設定するのでは無く、外から設定した例です。また、メイン画面用のオブジェクトをメニューに引き渡して、メニュー処理から本体画面のバインド処理を行えるようにしています。( 別に分ける必要はありませんが、分けたとしたらこんな感じかと思います )

package winofsql.jp;

import gueei.binding.Binder;
import gueei.binding.app.BindingActivity;
import android.os.Bundle;
import android.app.Activity;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.support.v4.app.NavUtils;

public class P1Activity extends BindingActivity  {

    private BindModel bindModel = new BindModel();
    private BindModelMenu bindModelMenu = new BindModelMenu(bindModel);
	
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        
        bindModel.button_value.set("ボタン");
        this.setAndBindRootView(R.layout.activity_p1, bindModel);
        this.setAndBindOptionsMenu(R.menu.activity_p1, bindModelMenu);
    }
    
}


メイン画面のバインド用クラス
package winofsql.jp;

import android.view.View;
import gueei.binding.Command;
import gueei.binding.observables.StringObservable;
import gueei.binding.pojo.PojoViewModel;
import gueei.binding.pojo.PojoViewModelHelper;

public class BindModel implements PojoViewModel {

    private PojoViewModelHelper helper = new PojoViewModelHelper();
    
    public final StringObservable hello = new StringObservable("こんにちは");
    public final StringObservable button_value = new StringObservable();

    public final Command Button_Click_1 = new Command() {
        @Override
        public void Invoke(View arg0, Object... arg1) {
        	hello.set("はじめまして");
        }
    };
    
    @Override
    public PojoViewModelHelper getHelper() {
        return helper;
    }
 
    @Override
    public void notifyPropertyChanged( String propertyName ) {
        helper.notifyPropertyChanged( propertyName );
    }

}


メニュー用のバインド用クラス
※ メイン画面用の中に書いて共用してもいいです
package winofsql.jp;

import android.view.View;
import gueei.binding.Command;
import gueei.binding.observables.StringObservable;
import gueei.binding.pojo.PojoViewModel;
import gueei.binding.pojo.PojoViewModelHelper;

public class BindModelMenu implements PojoViewModel {

    private PojoViewModelHelper helper = new PojoViewModelHelper();
	private BindModel bindModel;
    
    public final StringObservable bind_menu = new StringObservable("バインドメニュー");

    public final Command Menu_Click_1 = new Command() {
        @Override
        public void Invoke(View arg0, Object... arg1) {
        	BindModelMenu.this.bindModel.hello.set("メニューから変更");
        }
    };

	public BindModelMenu(BindModel bindModel) {
		this.bindModel = bindModel;
	}
 
    @Override
    public PojoViewModelHelper getHelper() {
        return helper;
    }
 
    @Override
    public void notifyPropertyChanged( String propertyName ) {
        helper.notifyPropertyChanged( propertyName );
    }

}


posted by lightbox at 2012-09-21 14:25 | Android | このブログの読者になる | 更新情報をチェックする

2012年06月14日


Eclipse で Android / 『クリックしてホームにショートカット』を誰でも作れる手順の解説



▼ 手順となると、やたらと画像が多くなるので全体像は NAVER で見れるようにしています

Eclipse で Android / 『クリックしてホームにショートカット』を誰でも作れる手順 - NAVER まとめ


そもそも、SDK のバッケージを隅々までみてもホームにショートカットを作成する処理が存在しないのですが、インターネットを見てもこの方法が紹介されているのみです。SDK 内のショートカットに関する処理の記述はいろいろ試しましたが、全く動作せずです。記述もすごくいいかげんで何言ってるか解らないものばかりでしたし、目的としてはこれが最も役に立つ内容なので。

いずれにしても、Eclipse と SDK(エミュレータ) が必要なのでそれについては以下のリンク先を参照して下さい。


■ Android エミュレータ単独利用Eclipse から Android エミュレータを使う



エミュレータは、4.0.3 も動作しますが、現状では 2.3.3 を使うか実機でテストするのが良いです(4.0.3 は日本語入力が面倒です)。特に実機はストレスなくすぐ確認できます。実機は以下のように中古を買うと言う手もあります。

■ Android 系、ソフマップ中古品揃え『9,800円〜』




重要なポイント

★ Java SDK のバージョン選択

1.5 と 1.6 と 1.7 があると思いますが、いろいろな意味で 1.6 を選択しておくほうが無難だと思います( 1.7 は他の開発で動かなかったりします )が、Eclipse がデフォルトで低いバージョンを選んでしまっている場合は、世の中のソースコードに書かれている @Override でエラーになる可能性が大きいので、意図的に 1.6 を選んでおきます。




★ AndroidManifest.xml に注意する

とにかく、当たり前のように関係してくる制限事項がここに絡んで来ます。

1) 最低必要な SDK バージョン( これより低いものをエミュレータで使うとエラー )
2) パーミッション( 宣言しないと使えないものが多々ある )
3) デバッグ用の設定等( 世間にたくさん転がってます )

ここでは、SDK では存在しないパーミッション(com.android.launcher.permission.INSTALL_SHORTCUT)を入力しておかないと動きません。




★ Eclipse のクセを知る

Eclipse の問題なんですが、こういうものなので受け入れるしかありません。エラーが表示されていても、それは実は Eclipse が消去するのを忘れている場合もあります。実行してやるととりあえず再度チェックしますから、動作確認も含めて時間がかかるかもしれませんが、実行したり、いったん終了したりしてるほうがトラブルを回避できる事が多いかもしれません。


★ Run Configurations と DDMS はいろいろ知っておく

結局エミュレータあってのデバッグなので、この二つの機能はできるだけ知っておく必要があります。多少でも知っておれば特に問題も無く前へ進めるはずです。



※ 上の画像は、画面のキャプチャの説明用ですが、デバイスが選択されています。これをしておかないと、LogCat すらまともに表示されません。
findViewById(R.id.button1).setOnClickListener(new OnClickListener() {
	@Override
	public void onClick(View v) {
		Toast.makeText(ShortCutActivity.this, "クリック1", Toast.LENGTH_LONG).show();
		Log.i("DEBUG", "クリック1");
	}
});
オーソドックスなクリックイベントの実装と、Toast と ログの出力です。
Intent targetIntent = new Intent( Intent.ACTION_MAIN );
targetIntent.setClassName( ShortCutActivity.this, "lightbox.test.ShortCutActivity" );

Intent intent = new Intent("com.android.launcher.action.INSTALL_SHORTCUT");
intent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, targetIntent);

Parcelable icon = Intent.ShortcutIconResource.fromContext(ShortCutActivity.this, R.drawable.ic_launcher);
intent.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, icon);
intent.putExtra(Intent.EXTRA_SHORTCUT_NAME, "ホームにショートカット");

ShortCutActivity.this.sendBroadcast(intent);
世間で紹介されているコードそのままですが、それ以外で com.android.launcher.action.INSTALL_SHORTCUT に関する記述をどこにも見つける事ができませんでした。( 謎です )


posted by lightbox at 2012-06-14 10:26 | Android | このブログの読者になる | 更新情報をチェックする

2012年06月03日


LiveSDK-for-Android ビルド手順

こちらが前提です

Eclipse から Android エミュレータを使う


SDK のダウンロードは liveservices/LiveSDK-for-Android ・ GitHub からです。SDK プロジェクトと Android でふつうに動く(Android 内の写真をアップロード可)サンプルプロジェクトが入っています。Java に慣れた人ならなんて事は無いんでしょうが、いくつも『わらじ』を履いているプログラマだと一発ではうまく行かないかもしれません。とにかく、手順を画像にするとものすごく多くなるので、NAVER に作成しました。

LiveSDK-for-Android ビルド手順 - NAVER まとめ

基本的には、添付されている README の英文通りにやっているのですが、README には エラーの対処がありません。しかし、AndroidManifest.xml にインターネットの参照許可の設定を行う事については書かれています。
1. With your project open,
 in the Package Explorer pane,
 open the AndroidManifest.xml file.
2. In the editor, click the Permissions tab.
3. Click Add.
4. Click Uses Permission, and then click OK.
5. the Name list, click android.permission.INTERNET.
6. Save the AndroidManifest.xml file.
posted by lightbox at 2012-06-03 21:23 | Android | このブログの読者になる | 更新情報をチェックする
Seesaa の各ページの表示について
Seesaa の 記事がたまに全く表示されない場合があります。その場合は、設定> 詳細設定> ブログ設定 で 最新の情報に更新の『実行ボタン』で記事やアーカイブが最新にビルドされます。

Seesaa のページで、アーカイブとタグページは要注意です。タグページはコンテンツが全く無い状態になりますし、アーカイブページも歯抜けページはコンテンツが存在しないのにページが表示されてしまいます。

また、カテゴリページもそういう意味では完全ではありません。『カテゴリID-番号』というフォーマットで表示されるページですが、実際存在するより大きな番号でも表示されてしまいます。

※ インデックスページのみ、実際の記事数を超えたページを指定しても最後のページが表示されるようです

対処としては、このようなヘルプ的な情報を固定でページの最後に表示するようにするといいでしょう。具体的には、メインの記事コンテンツの下に『自由形式』を追加し、アーカイブとカテゴリページでのみ表示するように設定し、コンテンツを用意するといいと思います。


※ エキスパートモードで表示しています

アーカイブとカテゴリページはこのように簡単に設定できますが、タグページは HTML 設定を直接変更して、以下の『タグページでのみ表示される内容』の記述方法で設定する必要があります

<% if:page_name eq 'archive' -%>
アーカイブページでのみ表示される内容
<% /if %>

<% if:page_name eq 'category' -%>
カテゴリページでのみ表示される内容
<% /if %>

<% if:page_name eq 'tag' -%>
タグページでのみ表示される内容
<% /if %>
この記述は、以下の場所で使用します
container 終わり



フリーフォントで簡単ロゴ作成
フリーフォントでボタン素材作成
フリーフォントで吹き出し画像作成
フリーフォントではんこ画像作成
ほぼ自由に利用できるフリーフォント
フリーフォントの書体見本とサンプル
画像を大きく見る為のウインドウを開くボタンの作成

CSS ドロップシャドウの参考デモ
イラストAC
ぱくたそ
写真素材 足成
フリーフォント一覧
utf8 文字ツール
右サイド 終わり
base 終わり