SpinachMedia

情報ポータル SpinachMedia です。

鴻鵠の志をまるで知らない。SpinachMedia
Develop
Network
Design

【iOS】delegateがよくわからん

delegateってなんやねん

慣れないobjective-cを触っていて

最近ようやく「delegate」の仕組みがわかってきた気がする。

 

「delegate」は委譲という意味だとはわかっているが

結局なんのことやらよくわかっていなかった。

 

結局の所、「実装を他のクラスに任せる」という意味であり

Java、Androidでいうところの「リスナーの定義」とほぼ同じであると理解できた。

 

解説

備忘録も兼ねて、画像付きで解説したい。

f:id:cacaoteacher:20141231210117p:plain

画面A(SceneA)から画面B(SceneB)に遷移するとして

以下のコードを組む。

・画面A側にDeligateメソッドの実装

・画面B側にDeligateメソッドの定義

 

これにより、以下の効果が得られる。

・画面Bからのキックで、画面Aのメソッドの実行

 

画面B側でdelegateメソッドの定義

f:id:cacaoteacher:20141231213708p:plain

まずヘッダ

 

 

ファイルで、外部に処理を委譲するメソッドを宣言する。

 

 

f:id:cacaoteacher:20141231214158p:plain

実装部では外部へ処理を委譲するタイミングを定義する

 

 画面A側にDeligateメソッドの実装

 

f:id:cacaoteacher:20150101183444p:plain

deligateを利用することをヘッダファイルで宣言

 

f:id:cacaoteacher:20150101183931p:plain

あとは実装

 

こうすることで

f:id:cacaoteacher:20150101184159p:plain

 

 ↑

SceneBから、SceneAで定義したメソッドを実行することができる。

 

デリゲートを定義しているSceneBからすると

処理を「委譲」する

 

デリゲートを実装しているSceneAからすると

処理を「引き受ける」形となる。

 

 

 

 

 

ZIPファイルのパスワードは何桁つければ安全?

何かとよく使用するパスワード付きZIPファイル。
メールでデータを送る際にとりあえず使用しますが、
はたしてどのようにパスワードをつければ安全なのでしょうか?
気になったので調査してみることにしました。

【目的】

パスワードはどのようにつければ安全なのか確認する。

【結果】

以下のことに気をつけること。

  • パスワードには英文字(大文字含む)を使用すること。
  • 記号は圧縮、解析ツールに依存するようなので、使用しないこと。
  • 英数字(大文字含む)であれば最低8桁のパスワードをつけること。
  • 英文字(大文字含む)であれば最低9桁のパスワードをつけること。
  • ファイルの重要度に応じてパスワードを長くすること。

【内容】

以下の流れで調査を行います。

 


  1. パスワードに使用するのは数字?英文字?記号?
  2. 1パターンあたりの検索時間は?
  3. 何桁が安全なの?

 

1.パスワードに使用するのは数字?英文字?記号?

ZIPファイルでは、一般的に数字・英文字・記号が使用可能です。
1桁あたりのパターンは以下の通りです。

 

種類パターン数
数字 0~9 10
英文字(大文字含む) a~z 52
記号 193

 

当然ですが、
1桁あたりのパターン数が多いほうが
"解析に時間を要する=安全"となります。
よって、数字、英文字、記号を組み合わせたパスワードが最も安全といえます。

 

ただし、記号は圧縮・解凍ソフトによって使用できない場合があるそうなので、
社外に出す場合は、数字と英文字を組み合わせたパスワードが最適です。

 

2. 1パターンあたりの検索時間は?

下記環境にて数字8桁にて測定を行いました。

この測定結果から、1パターンあたりの検索時間を求めます。

 

試験環境
Windows7 64bit
Core i5 2.90GHz
メモリ 8GB
Lhaplus Ver1.59 ("ZIP パスワード探索"機能を使用)

測定結果

パスワード解析時間※
00000000 00m32s
50000000 02m55s
99999999 05m27s
平均 02m58s

※0から探索開始した場合

数字8桁の場合は、組み合わせが
10^8 = 100,000,000 = 1億通り
1パターンあたり 1.78μs

 

3.何桁が安全なの?

数字・英文字・英数字の6~10桁時の組み合わせ数、

0から探索した場合に要する時間を計算した結果を以下に示します。

(y:年、d:日、h:時、m:分、s:秒 を意味しています)

 

桁数数字英文字(大文字含む)英数字(大文字含む)
1 10 - 52 - 62 -
6 1000000 1s 19770609664 9h46m31s 56800235584 1d4h5m4s
7 10000000 17s 1028071702528 21d4h19m27s 3521614606208 72d13h14m33s
8 100000000 2m57s 53459728531456 3y6d8h51m56s 218340105584896 12y118d5h3m7s
9 1000000000 29m40s 2779905883635712 156y331d5h1m12s 13537086546263552 764y29d1h14m12s
10 10000000000 4h56m40s 144555105949057024 8159y67d21h3m9s 839299365868340224 47372y341d4h40m45s

 

表に記載されている時間は、総組み合わせ数から算出しているので、

パスワードの値によってはより早い時間で解析ができます。

(999と1000では1桁違うが、探索時間はほぼ同じ)

 

私の感覚から、1ヶ月以上解析に時間がかかるのであれば、

安全といえるのではないでしょうか?

(ファイルの重要度にもよりますが・・・)

 

以上より、

  • パスワードは英文字(大文字含む)を使用する
  • 英文字(大文字含む)のみであれば9桁以上とする
  • 英数字(大文字含む)であれば8桁以上とする

 ことが重要だと思います。

 

 

 

Android の WebView 、画像を取得する代わりにローカル画像を表示


MVNOのmineoを使ってみたのですが、月々1GBの通信に抑えるのは厳しいので

余計な画像や広告を取得しないようなブラウザはないものか、とちょっと作ってみました。

仕様

①png、jpeg、gifのhttpリクエストを遮断

②代わりにローカルのダミー画像を表示する。

③ダミー画像をタップすることで本来表示されるべきだった画像を取得する。

 

実装

調べてみたらAndroidのWebViewクラスに、以下のメソッドがありました。
shouldInterceptRequest(WebView view,String url) ;

 HTTPリクエストを行う直前に処理を挟めるみたいです。

これをオーバーライドして以下のように処理を変更。

 拡張子をみてリクエストを遮断、代わりにローカル画像を表示

 


    @Override
	public WebResourceResponse shouldInterceptRequest(WebView view,String url) {
		if (url.endsWith(".jpg") || url.endsWith(".png")|| url.endsWith(".gif")) {
		    // URLの頭に「a」が付いている場合は、正しい画像を取得する。
			if (url.substring(0, 1).equals("a")) {
				url = url.replaceAll("^a", "");
				Log.d("this", url);
				URL urlData;
				InputStream in = null;
				try {
					urlData = new URL(url);
					in = urlData.openStream();
				} catch (MalformedURLException e) {
				} catch (IOException e) {
				}
				WebResourceResponse webResourceResponse = new WebResourceResponse("image/jpeg", null, in);
				return webResourceResponse;
			}

			// ローカルの画像を返す。
			InputStream inputStream = res.openRawResource(R.drawable.noimage);
			WebResourceResponse webResourceResponse = new WebResourceResponse("image/jpeg", null, inputStream);
			return webResourceResponse;
		} else {
			return super.shouldInterceptRequest(view, url);
		}
	}    

 ブックマークレットを実行する処理を追加

 imgタグにクリックイベントを追加します。

myWebView.loadUrl("javascript:" +
		"var a=document.querySelectorAll('img');" + 
		"   for(i=0;i < a.length;i++){" + 
		"    a[i].addEventListener('click', " + 
		"      function(event) {" + 
		"       this.src = 'a' + this.src;" + 
		"      },false);" + 
		"   }"
	);    

 

こんな感じになりました。

 


SpinachMedia WebViewに対してブックマークレットを実行 - YouTube

 

 

以下、ソース。

 

package com.example.jp.spinach.packet.save.browser;

import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;

import android.app.Activity;
import android.content.res.Resources;
import android.os.Bundle;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.webkit.JsResult;
import android.webkit.WebChromeClient;
import android.webkit.WebResourceResponse;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.Toast;

public class MainActivity extends Activity {

	WebView myWebView;

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

		final Resources res = getResources();

		// レイアウトで指定したWebViewのIDを指定する。
		myWebView = (WebView) findViewById(R.id.webView1);

		// リンクをタップしたときに標準ブラウザを起動させない
		myWebView.setWebViewClient(new WebViewClient() {
			@Override
			public WebResourceResponse shouldInterceptRequest(WebView view,
					String url) {
				if (url.endsWith(".jpg") || url.endsWith(".png")
						|| url.endsWith(".gif")) {

					// URLの頭に「a」が付いている場合は、正しい画像を取得する。
					if (url.substring(0, 1).equals("a")) {
						url = url.replaceAll("^a", "");
						Log.d("this", url);
						URL urlData;
						InputStream in = null;
						try {
							urlData = new URL(url);
							in = urlData.openStream();
						} catch (MalformedURLException e) {
						} catch (IOException e) {
						}

						WebResourceResponse webResourceResponse = new WebResourceResponse(
								"image/jpeg", null, in);
						return webResourceResponse;
					}

					// ローカルの画像を返す。
					InputStream inputStream = res
							.openRawResource(R.drawable.noimage);
					WebResourceResponse webResourceResponse = new WebResourceResponse(
							"image/jpeg", null, inputStream);
					return webResourceResponse;
				} else {
					return super.shouldInterceptRequest(view, url);
				}
			}
		});

		WebSettings settings = myWebView.getSettings();
		settings.setJavaScriptEnabled(true);

		myWebView.setWebChromeClient(new WebChromeClient() {
			public boolean onJsAlert(final WebView view, final String url,
					final String message, JsResult result) {
				Toast.makeText(MainActivity.this, message, 3000).show();
				result.confirm();
				return true;
			}
		});

		// 最初にYahoo! Japanのページを表示する。
		myWebView.loadUrl("http://www.spinachmedia.info/entry/2014/09/14/230449");

	}

	@Override
	public boolean onCreateOptionsMenu(Menu menu) {
		getMenuInflater().inflate(R.menu.main, menu);
		return true;
	}

	@Override
	public boolean onOptionsItemSelected(MenuItem item) {
		int id = item.getItemId();
		if (id == R.id.action_settings) {
			Log.d("test", "javascript");
			myWebView.loadUrl("javascript:" +
					"var a=document.querySelectorAll('img');" + 
					"   for(i=0;i < a.length;i++){" + 
					"    a[i].addEventListener('click', " + 
					"      function(event) {" + 
					"       this.src = 'a' + this.src;" + 
					"      },false);" + 
					"   }"
					);
			return true;
		}
		return super.onOptionsItemSelected(item);
	}
}
    

【Androidでゲーム的なアニメーションの実装】第2回.サークルセレクタ編

 

前回(↓)に続いて第二回です。


【Androidでゲーム的なアニメーションの実装】第1回.テキストウィンドウ編 - SpinachMedia

 

このままちっちゃいモジュールをたくさん使って、豪華な画面のゲームを作ってやろうと思います。

何をつくろうかなぁ。

 

今回は最近流行っているUI?を実装してみます。

画面内のアイコンをタップすると、花びらが開くようにアイコンが円形に飛び出すUIですね。

なんていうUIなんでしょうか?

 

本章では勝手に「サークルセレクタ」と名づけて実装しています。

 

【Androidでゲーム的なアニメーションの実装】

第1回.サークルセレクタ編

 

勝手に名前をつけてしまいましたが…

「Path」や「matomemo」あたりで驚いたUIです。

以下にスクリーンショット。

 

左の画面の右下のアイコンをタップすると、

右の画面の様に「バラっと」開きます。

f:id:cacaoteacher:20140914230027p:plain  →   f:id:cacaoteacher:20140914230028p:plain

 

なんなんでしょうね。これ。

かっこいい。

 

いきなり実装結果

 

 こんなかんじになりました。

アイコンは「ぴぽや倉庫」様から拝借しております。

 

 ポイント

  1. 親ボタンをタップすると、子要素が円形に飛び出す。
  2. 一度バウンドする。
  3. 開ききるまでに、親要素が90度回転する。
  4. 子要素もどうやら回転している。

むむ…難しいな…。

 

今回の開発条件

  • Android
  • SurfaceView

 

開発方針

今回、独自に以下の4つのクラスを定義します。

 

・CircleSelecterAnimationクラス

メインクラス。

アニメーションを実行し、動的に座標などを操作する。

 

・CIrcleSelecterParentクラス

親ボタンクラス。

 

・CircleSelecterChildクラス

子ボタンクラス。

 

・AnimationTypeクラス

親ボタンの配置場所を設定するENUMクラス。

今回は左下に親ボタンを配置する実装のみ。

 

全体としてはこんな図でしょうか・・・?

わかりづらいな・・・。

 

f:id:cacaoteacher:20140914232359p:plain

 

 今回使った小技

 滑らかな動きを再現するにあたって、以下の様な考え方をしています。

 

①それぞれの子ボタンは、3種類の座標情報を持ちます。

  • startRect・・・開く前の自分の座標
  • endRect・・・一番開いた時の自分の座標
  • viewRect・・・描画に利用する座標

 

②viewRectの位置を、startRect と endRect の間の直線で行き来させる。

f:id:cacaoteacher:20140914233216p:plain

 

③滑らかに行き来させる為に正弦波(Sin)を利用する。

 

「endRect」と「startRect」との差に対して、

正弦波の計算で導き出した「0~1」の少数を掛けて座標を決定する。

f:id:cacaoteacher:20140914234140p:plain

 

f:id:cacaoteacher:20140914233716p:plain

 

④各要素の配置は、正弦余弦を利用した円座標に配置する。

 

 ソースコード

 色々と省いてます。

完全版は画面最下部のGithubをご覧下さい。

 

MainActivity.java

    public class MainActivity extends Activity {

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		MainView view = new MainView(this);
		setContentView(view);
	}
}

 

MainView.java

    public class MainView extends SurfaceView implements SurfaceHolder.Callback,
		Runnable {
		
		//変数は割愛します。
		
		public MainView(Activity activity) {
		super(activity);
		holder = getHolder();
		holder.addCallback(this);
		this.activity = activity;
		res = getContext().getResources();
		paint = new Paint();

		//サークルセレクターを初期化。
		//サークルセレクターの表示位置を画面の左下に設定。
		//コンストラクタはこのタイミングで動作させる必要がある。
		//(SurafaceView開始後では、CircleSelecterAnimation内のHandlerと競合してしまい、インスタンス化できない。)
		cercleSelecterAnimation = new CircleSelecterAnimation(
				AnimationType.LeftBottom);
	}
	
	@Override
	public void run() {

		//画面サイズの取得
		scWidth = getWidth();
		scHeight = getHeight();
		
		//メインのボタンを登録する。
		cercleSelecterAnimation.addMainCircle(R.drawable.s_menu_parent, res);
		//メインボタンのサイズを登録する為に、画面サイズを渡す。
		cercleSelecterAnimation.setMainCircleRect(scWidth, scHeight);
		
		//子要素を追加する。画面のスペースと、メモリの許す限りいくらでも追加可。
		//3~5個を推奨。
		cercleSelecterAnimation.addChildCircle(R.drawable.s_menu_child_01, res);
		cercleSelecterAnimation.addChildCircle(R.drawable.s_menu_child_02, res);
		cercleSelecterAnimation.addChildCircle(R.drawable.s_menu_child_03, res);
		cercleSelecterAnimation.addChildCircle(R.drawable.s_menu_child_04, res);

		
		while (thread != null) {
			try {
				canvas = holder.lockCanvas();
				canvas.drawColor(Color.WHITE);

				// サークルボタンの子要素の描画
				for(CircleSelecterChild circle:cercleSelecterAnimation.circleSelecterList){
					canvas.drawBitmap(circle.image,
							circle.drawRect,
							circle.viewRect, paint);
				}
				
				// サークルボタンの親ボタン描画
				canvas.drawBitmap(cercleSelecterAnimation.mainCircle.image,
						cercleSelecterAnimation.mainCircle.matrix,
						paint);
				
			} catch (Exception e) {
			} finally {
			}
		}
	}
}
	
	

 

 

CircleSelecterAnimation.java

class CircleSelecterAnimation {
	
	/**
	 * コンストラクタ。
	 * 親ボタンの表示位置を決める。
	 * @param type
	 */
	public CircleSelecterAnimation(AnimationType type) {
		this.type = type;
		circleSelecterList = new ArrayList<circleselecterchild>();
	}
	
	/**
	 * サークルセレクタのメインボタンを登録する。
	 * @param id
	 * @param res
	 */
	public void addMainCircle(int id,Resources res){
		mainCircle = new CircleSelecterParent();
		mainCircle.image = BitmapFactory.decodeResource(res, id);
		mainCircle.drawRect = new Rect(0, 0, mainCircle.image.getWidth(), mainCircle.image.getHeight());
		mainCircle.matrix = new Matrix();
		mainCircle.matrix.postRotate(0);
	}
	
	/**
	 * 画面サイズを渡してボタンの描画領域を決定する。
	 * addMainCircleの後に実行される必要がある。
	 * @param screenWidth
	 * @param screenHeight
	 */
	public void setMainCircleRect(int screenWidth, int screenHeight) {
		this.screenWidth = screenWidth;
		this.screenHeight = screenHeight;
		switch(type){
			case LeftBottom:
				mainCircle.viewRect = new Rect(0, screenHeight - mainCircle.image.getHeight(), mainCircle.image.getWidth(), screenHeight);
				mainCircle.matrix.setTranslate(0, screenHeight - mainCircle.image.getHeight());
				break;
			case CenterBottom:
				//TODO 実装せねば
				break;
			case RightBottom:
				//TODO 実装せねば
				break;
		}
	}
	
	/**
	 * サークルセレクタの子要素を追加する。
	 * 2~5個を推奨。
	 * 試しに50個やってみたらOutOfMemory.
	 * @param id
	 * @param res
	 */
	public void addChildCircle(int id,Resources res){
		CircleSelecterChild circle = new CircleSelecterChild();
		circle.image = BitmapFactory.decodeResource(res, id);
		circleSelecterList.add(circle);
		settingPlace();
	}
	
	private void settingPlace(){
		for(int i = 0;i < circleSelecterList.size();i++){
			CircleSelecterChild circle = circleSelecterList.get(i);
			switch(type){
				case LeftBottom:
					
					//初期位置の設定
					circle.startRect = new Rect(0, screenHeight - circle.image.getHeight(), 0 + circle.image.getWidth(), circle.image.getHeight());
					
					//描画位置の設定(初期位置)
					circle.viewRect = new Rect(0, screenHeight - circle.image.getHeight(), 0 + circle.image.getWidth(), screenHeight);
					
					//画像内の描画位置の決定
					circle.drawRect = new Rect(0, 0, circle.image.getWidth(), circle.image.getHeight());
					
					//リストの順番によって、座標を変える。
					float rot = 90/(circleSelecterList.size() + 1);
					rot = rot * (i+1);
					float rad = (float) (rot * Math.PI / 180);
					double x = Math.sin(rad);
					double y = Math.cos(rad);
					
					//x = 0 ~ 1 の少数。
					//画面幅 * x  -  ボタンの横幅 /2 - 親ボタンの横幅  / 2
					int left = (int)((screenWidth*x - circle.image.getWidth()/2) - mainCircle.image.getWidth()/2);
					int top = (int)((-screenWidth*y) + screenHeight - circle.image.getHeight() / 2 + mainCircle.image.getWidth()/2);
					
					//最終的な位置を決定
					circle.endRect = new Rect(
							left,
							top,
							left + circle.image.getWidth(),
							top + circle.image.getHeight()
							);
					break;
				case CenterBottom:
					//TODO 実装せねば
					break;
				case RightBottom:
					//TODO 実装せねば
					break;
			}
			
		}
	}
	
	
	
	
	/**
	 * 表示アニメーションハンドラーのキックメソッド
	 * 実行時にハンドラーが起動済で無いことが条件
	 */
	public void startOpenAnimation() {
		if(handlerIsRunning){
			//例外でも投げるか・・・?
		}else{
			handlerIsRunning = true;
			for(CircleSelecterChild circle:circleSelecterList){
				circle.rot = 0;
			}
			mainCircle.selfRadious = 0;
			openAnimation.sendEmptyMessageDelayed(1, 5);
		}
	}

	/**
	 * サークルセレクターアニメーションハンドラー
	 * 0度から130度までの正弦の値を係数にしてアニメーションを行う。
	 */
	private final Handler openAnimation = new Handler() {
		@Override
		public void dispatchMessage(Message msg) {
			if (msg.what == 1) {
				
				//全アニメーションが終わるとtrueになる。
				Boolean handlerIsEnd = false;
				
				//メインボタンの回転角を追加
				mainCircle.selfRadious++;
				
				//90度になるまで回転させ続ける。
				if(mainCircle.selfRadious < 90){
					//マトリックスに値を設定
					mainCircle.matrix.postRotate(
						//回転角を1度追加
							1,
						//回転の中心を設定。
						//今描画している領域から、画像の真ん中の位置まで中心をずらす。
							mainCircle.viewRect.left + mainCircle.image.getWidth()/2,
							mainCircle.viewRect.top + mainCircle.image.getHeight()/2
						);
				}
				
				
				//サークルセレクタの子要素に
				for(int i = 0;i < circleSelecterList.size();i++){
					
					CircleSelecterChild circle = circleSelecterList.get(i);
					
					//ひとつ前のボタンとの30度分の差がついてからアニメーションをスタートする。
					if(i > 0){
						CircleSelecterChild beforeCircle = circleSelecterList.get(i-1);
						if(circle.rot == 0 
								&& beforeCircle.rot - circle.rot < 30){
							break;
						}
					}
					
					//ボタンの動きが正弦波の130度を超えた時点で動作を止める。
					//(アニメーション的には目的地を一旦通りすぎる様に見える。)
					if(circle.rot >= 130){
						handlerIsEnd = true;
					}else{
						circle.rot++;
						float rad = (float) (circle.rot * Math.PI / 180);
						
						Rect startRect = circle.startRect;
						Rect endRect = circle.endRect;
						double x = Math.sin(rad);
						int left = startRect.left + (int)((endRect.left - startRect.left) * x);
						int top = startRect.top + (int)((endRect.top - startRect.top) * x);
						circle.viewRect = new Rect(
								left,
								top,
								left + circle.image.getWidth(),
								top + circle.image.getHeight()
								);
						handlerIsEnd = false;
					}
					
				}
				
				//45度まで処理。
				if (!handlerIsEnd) {
					sendEmptyMessageDelayed(1, 3);
				}else{
					handlerIsRunning = false;
					isOpen = true;
				}
			} else {
				super.dispatchMessage(msg);
			}
		}
	};

}

 

上記のコードは色々と省いているので、以下のコードを参考にしてください。


spinachmedia/CircleSelecter · GitHub

 

【Androidでゲーム的なアニメーションの実装】第1回.テキストウィンドウ編

昔から趣味でゲームを作ったり、作りかけたり、作っては作りかけたり…。

たくさんやってはいますが、どうにも私の作るゲームは見た目がショボイ

 

そこで、アプリの見た目をリッチにする方法を模索中です。

 

毎回少しずついろんなゲームの表現方法を再現し、ライブラリできればいいなと思っております。

今回はゲームの基本と思われる、テキストウィンドウの開閉アニメーションを実装してみました。

 

あんまりこういう細かいアニメーションに関して書かれているブログって無いよね…?

当たり前すぎて書かないのかな。

 

【Androidでゲーム的なアニメーションの実装】

第1回.テキストウィンドウ編

「テキストウィンドウってなんだ?」って人の為に以下のスクリーンショット。

 

f:id:cacaoteacher:20140907144625p:plain

 

パズドラとかの「コレ」です。

あとはRPGとかでキャラクターが喋ったりするやつですね。

 

コレ、開くときは「点」から始まって

閉じる時も「点」に収束していきます。

 

これを実装してみます。

 

いきなり実装結果

こうなりました。

 ポイント

  1. 等速運動ではない。(気がする)
  2. 開き切らないと、閉じるアニメーションは始められない。(気がする)

 

 

今回の開発条件

  • Android
  • SurfaceView

 

 

開発方針

今回、独自に「WindowAnimationクラス」を定義します。

WindowAnimationクラスが保持するのは以下のプロパティです。

  • 表示する画像(Bitmap)
  • 画像のどの部分を表示するかの領域クラス(Rect)
  • 画像を表示する場所の領域クラス(Rect)
  • 表示領域を動的に変えるためのループ機構(Handler)

この「WindowAnimationクラス」をインスタンス化して、SurfaceViewの中に持たせます。

あとはSurfaceViewのループ機構(runメソッド)の中から描画するだけですね。

f:id:cacaoteacher:20140907150451g:plain

 

 

 

 

ソースコード

まず、独自クラスを作成して、その中に画像を設定できるようにします。

 


public class WindowAnimation {
	
	public Bitmap window;
	
	public WindowAnimation(Resources res, int resourceId) {
		window = BitmapFactory.decodeResource(res, resourceId);
	}

}

 

続いて、設定した画像のどの部分を表示するのかを設定できるようにしましょう。

 


public class WindowAnimation {
	
	public Bitmap window;
	
	public WindowAnimation(Resources res, int resourceId) {
		window = BitmapFactory.decodeResource(res, resourceId);
	}
	
	
	public Rect drawRect;
	
	public void initializeSprite(int left, int top, int width, int height) {
		drawRect = new Rect(left, top, width, height);
	}

}

 

さらに、今度は「画面上のどこに描画するのかを設定できるようにします。」

 


public class WindowAnimation {
	
	public Bitmap window;
	
	public WindowAnimation(Resources res, int resourceId) {
		window = BitmapFactory.decodeResource(res, resourceId);
	}
	
	
	public Rect drawRect;
	
	public void initializeSprite(int left, int top, int width, int height) {
		drawRect = new Rect(left, top, width, height);
	}
	
	public Rect viewRect;
	private Rect startRect;
	private Rect endRect;
	
	public void setViewRect(int left, int top, int right, int bottom) {
		startRect = new Rect((left + right) / 2, (top + bottom) / 2,
				(left + right) / 2, (top + bottom) / 2);
		endRect = new Rect(left, top, right, bottom);

		viewRect = new Rect((left + right) / 2, (top + bottom) / 2,
				(left + right) / 2, (top + bottom) / 2);
	}

}

 

ちょっと複雑になっていますが、3つの値を設定しています。

  • startRect … アニメーション開始時の画像の最初の大きさ
  • endRect … アニメーション終了時の画像の最終的な大きさ
  • viewRect … 実際に大きさが変わっていく動的な値。描画に利用するのはこの値のみ。

要はviewRectをちょっとずつ大きさを変えていきましょうという動きです。

 

 

次はアニメーション処理を実装してみます。

 


public class WindowAnimation {
	
	public Bitmap window;
	
	public WindowAnimation(Resources res, int resourceId) {
		window = BitmapFactory.decodeResource(res, resourceId);
	}
	
	
	public Rect drawRect;
	
	public void initializeSprite(int left, int top, int width, int height) {
		drawRect = new Rect(left, top, width, height);
	}
	
  public Rect viewRect;
	private Rect startRect;
	private Rect endRect;
	
	public void setViewRect(int left, int top, int right, int bottom) {
		startRect = new Rect((left + right) / 2, (top + bottom) / 2,
				(left + right) / 2, (top + bottom) / 2);
		endRect = new Rect(left, top, right, bottom);

		viewRect = new Rect((left + right) / 2, (top + bottom) / 2,
				(left + right) / 2, (top + bottom) / 2);
	}
	
	
  public void startOpenAnimation() {
		rot = 0;
		openAnimation.sendEmptyMessageDelayed(1, 5);
	}
	
	private final Handler openAnimation = new Handler() {
		@Override
		public void dispatchMessage(Message msg) {
			if (msg.what == 1) {
				
				handlerFlg = true;

				rot = rot + 1;

				float rad = (float) (rot * Math.PI / 180) * 2;
				float size = (float) Math.sin(rad);

				// 幅、高さ計算
				int width = endRect.right - endRect.left;
				int height = endRect.bottom - endRect.top;

				// 表示幅、高さ計算
				float vWidth = width * size;
				float vHeight = height * size;

				//描画領域の計算
				viewRect = new Rect((int) (startRect.left - vWidth / 2),
						(int) (startRect.top - vHeight / 2),
						(int) (startRect.left + vWidth / 2),
						(int) (startRect.top + vHeight / 2));
				
				//45度まで処理。
				if (rot < 45) {
					sendEmptyMessageDelayed(1, 5);
				}else{
				}
			} else {
				super.dispatchMessage(msg);
			}
		}
	};
	
}

 

正弦波の動きを利用しています。(Sin)

ちょっとこのあたりはまた別の機会に解説します。

 

とにかくこれで、いい感じのアニメーションでウィンドウが開くようになります。

 

あとはSurfaceViewからアニメーションをキックしつつ描画するだけですね。

 


    public MainView(Activity activity) {
        …
        windowAnimation = new WindowAnimation(res,R.drawable.s_window_sample);
    }
    
    @Override
    public void run() {
    	
    	scWidth = getWidth();
    	scHeight = getHeight();
        windowAnimation.initializeSprite(0, 0, windowAnimation.window.getWidth(), windowAnimation.window.getHeight());
    	windowAnimation.setViewRect(0, 0, scWidth, (int)(scHeight/3));
    	windowAnimation.startOpenAnimation();
    	
        while(thread != null){
            try{
            	canvas = holder.lockCanvas();
            	canvas.drawColor(Color.WHITE);
            	canvas.drawBitmap(windowAnimation.window, windowAnimation.drawRect, windowAnimation.viewRect, paint);
            }catch(Exception e){
                …
            }finally{
                …
            }
        }
    }

 

これでパズドラっぽい(というかゲームっぽい)ウィンドウが開くアニメーションが実装できたはずです!

 

 

あとは閉じるアニメーションが必要だったりの制御が必要なのですが、

それは全体のソースコードを見てみてください。

全体ソースコード

 Githubにあげたから見てみてください。


spinachmedia/WindowAnimation · GitHub

 

【iOS】画像の使い方、画像ファイルの配置方法

iOSのアプリを作ってみてはいるが

Androidと違って画像の使い方がすごくわかりづらい。

 

まずどこに置けばいいの?

 

からはじまって

 

どうやってつかうの?

 

というド素人状態

 

よって、以下に手順化。

 

①Xcodeのプロジェクト内に画像ファイルをドロップする。

「back.jpeg」をプロジェクト内にドロップ。

f:id:cacaoteacher:20140823234647p:plain

 

②画面の左下のフィルムみたいなアイコンを押すとなんかこんな感じに見える。

 

f:id:cacaoteacher:20140823234649p:plain

 

③あとはソースをこう

 

    //背景画像の設定
    UIImage *backgroundImage = [UIImage imageNamed:@"back.jpeg"];
    viewTable.backgroundView = [[UIImageView alloc] initWithImage:backgroundImage];
        



うん。
画像の置き場所、ほんとにこれでいいのかって思うけど
とりあえず以上。

【iOS】heightForRowAtIndexPathメソッドが呼び出されない

iOSのアプリを初めて作ってみているが

 

UITableViewの各セルの高さを設定するためのメソッド

「heightForRowAtIndexPath」がどうやっても呼び出されない。

 

日本語の記事ではどこを調べても解答がなかったが

海外サイトで解答を見つけることが出来た。

 

恥ずかしながら以下の記述が漏れていたようだ。

ViewController.m
    viewTable.delegate = self;

このメソッド自体を入れてなかった。 あと以下の赤字の部分を入れてなかった。

ViewController.h
    @interface ViewController : UIViewController <UITableViewDelegate, UITableViewDataSource>

うん。これでheightForRowAtIndexPathが呼ばれるようになり、各セルの高さを動的に決められるようになった。

以下、コード全文。 ViewController.h

//
//  ViewController.h
//  TimeLine
//
//  Created by apple on 2014/08/23.
//  Copyright (c) 2014年 apple. All rights reserved.
//

#import <UIKit/UIKit.h>

@interface ViewController : UIViewController <UITableViewDelegate, UITableViewDataSource>

    @property (weak, nonatomic) IBOutlet UITableView *viewTable;

@end

ViewController.m

    //
//  ViewController.m
//  TimeLine
//
//  Created by apple on 2014/08/23.
//  Copyright (c) 2014年 apple. All rights reserved.
//

#import "ViewController.h"
#import "Cassette.h"

@interface ViewController ()

@property (weak, nonatomic) IBOutlet UIButton *buttonAdd;

@end

@implementation ViewController

@synthesize viewTable;

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    return 5;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
    
    
    UITableViewCell *tvcell = [tableView dequeueReusableCellWithIdentifier: @"cid"];
    
    tvcell.opaque = NO;
    tvcell.backgroundColor = [UIColor colorWithWhite:1.0f alpha:0.0f];
    
    if (tvcell == nil) {
        tvcell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault
                                        reuseIdentifier: @"cid"];
    }
    tvcell.text = "testText";
    return tvcell;
    
}

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
    //高さを返す
    return 80;
}


- (void)viewDidLoad
{
    [super viewDidLoad];
	// Do any additional setup after loading the view, typically from a nib.
    list = [[NSMutableArray alloc]init];
    viewTable.delegate = self;
    viewTable.dataSource = self;
    
}

- (void)didReceiveMemoryWarning
{
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

@end

以上。