MacのTerminalでファイル内テキスト検索&テキスト置換
テキスト検索
findコマンド grepコマンドを使用します
find ディレクトリ | xargs grep -rn "検索したい文字列"
-r はディレクトリ内を再帰的に検索するオプション
-nは検索結果に行数を表示するオプション
- 例
find ~/Desktop -name "*.swift" | grep -rn "kerusan"
デスクトップ内の.swiftファイル内のkerusanという文字列を検索
テキスト置換
gsedコマンドを使用します
brewコマンドでインストールできます
brew install gnu-sed
gsed -i "" -e "s/置換前の文字列/置換後の文字列/g" ファイル
-i は上書き保存するオプション ( --in-place ) -e はスクリプトを実行してバックアップを作成するオプション (--expression=script) バックアップ作成は強制されてしまうのですが -i ""とすることでバックアップファイルを作らずにコマンドを実行することができます
- 例
gsed -i "" -e "s/keru/san/g" kerusan.txt
kerusan.txt内のkeruをsanに置換
複数ファイルのテキスト置換
上記コマンドの組み合わせで実現します
find ディレクトリ | xargs grep -rl "置換前のテキスト" | xargs gsed -i "" -e "s/置換前のテキスト/置換後のテキスト/g"
grepのオプションが-rlになっていますが
-l というのは文字列が存在するファイル名のみを出力するオプションです
これによって検索したいファイル名のみを後続のgsedコマンドに渡すことができます
- 例
find ~/Desktop -name "*.swift" | xargs grep -rl "keru" | xargs gsed -i "" -e "s/keru/san/g"
デスクトップの.swiftファイル内全ての文字列"keru"を"san"に置換する
参考
Macでgrepした結果をgnu-sedを使ってsedする - 塩焼きブログ
[Linux][Mac]複数ファイルまとめて特定の文字列を検索するコマンド(2種類) | SATOPEDIA
MacとLinuxでは、sedの動作が異なるので注意 | ITを使っていこう
追記
grepのlオプション(マッチするファイル名のみ列挙)について久しぶりに使おうとすると忘れがちなのでメモ --files-with-matches というオプションの省略系が -l という覚えづらさ笑 files の lと覚えておきましょう
コマンドのオプションは何でもしばらく使わないと忘れてしまいますよね 省略してない形式も一緒に覚えて意味で捉えておくと良いかもしれませんということで追記でした
iOSアプリで縦書きテキストラベルを実装するときに参考にしたサイトまとめ
基本的にこのライブラリを利用します。
https://github.com/TTTAttributedLabel/TTTAttributedLabel
TTTAttributedLabel内部ではCoreTextが使用されているのでこちらも読みました。
Swift3でCoreTextでの縦書きdebuyan.wordpress.com
文字を貼り付けるためのViewサイズを計算するために以下サイトを参考にしました。
縦書きテキストをリサイクルして利用するためにUICollectionViewで実装しました。
注意点
表示するフォントをHiragino-SansW3に指定すると記号やアルファベットの表示が自然な表示になります。
文字を縦に回転させているのですが、上記の方法で文字全体の大きさを計算すると文字が横の状態でのサイズの計算になってしまうので最終的に表示するViewの大きさは細かい調整をしないといけませんでした。
Androidで動画プレイヤーアプリを作る
Androidで動画プレイヤーアプリを作る
VideoViewというクラスを使えば簡単な動画プレイヤーを作ることはできるのですが
今回は勉強のためにあえてVideoViewの内部で使われているSurfaceViewとMediaPlayerを使用して動画プレイヤーを作っていきます
主に使用するパッケージやクラス
開発環境
Android 5.0 ~
Android Studio 2.3.3
動画再生までの流れ
- SurfaceViewを有効にする
- MediaPlayerを初期化する
- SurfaceViewにPlayerを紐づける
- 再生中の時間表示等のためのスレッドを開始させる
- そのスレッドでPlayerの状態を監視して画面表示を更新していく
準備
Manifestに追加する
<uses-permission android:name="android.permission.INTERNET"/>
MoviePlayerFragmentの実装
Fragmentに動画URIを渡す
MoviePlayerFragmentにイニシャライズメソッドを定義してFragment生成時に動画のURIを渡す処理をする
.. 省略 private String mPlayingUri; private static final String PLAYING_URI_KEY = "PLAYING_URI"; .. 省略 public static MoviePlayerFragment newInstance(String uri) { Bundle args = new Bundle(); MoviePlayerFragment fragment = new MoviePlayerFragment(); args.putString(PLAYING_URI_KEY,uri); fragment.setArguments(args); return fragment; } .. 省略 @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.fragment_movie_player, container, false); mPlayingUri = getArguments().getString(PLAYING_URI_KEY); .. 省略
SurfaceViewの準備をする
動画を描画するView
.. 省略 @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.fragment_movie_player, container, false); mPlayingUri = getArguments().getString(PLAYING_URI_KEY); mSurfaceView = (SurfaceView) view.findViewById(R.id.surface_view); mSurfaceHolder = mSurfaceView.getHolder(); // ->(1) mSurfaceHolder.addCallback(mSurfaceCallback); .. 省略 SurfaceHolder.Callback mSurfaceCallback = new SurfaceHolder.Callback() { @Override public void surfaceCreated(SurfaceHolder surfaceHolder) { // ->(2) mPlayPauseButton.setAlpha(1); // ->(3) mLoopButton.setAlpha(1); mSeekBar.setAlpha(1); } @Override public void surfaceChanged(SurfaceHolder surfaceHolder, int i, int i1, int i2) { // ->(4) } @Override public void surfaceDestroyed(SurfaceHolder surfaceHolder) { // ->(5) } }; .. 省略
(1) SurfaceHolderを取得してコールバックを登録
(2) SurfaceViewの準備が完了したタイミング
(3) 再生コントロールのUIを使用可能にする
(4) SurfaceViewの状態が変更された
(5) SurfaceViewが破棄された
MediaPlayerの準備をする
.. 省略 mMediaPlayer = new MediaPlayer(); mMediaPlayer.setOnVideoSizeChangedListener(new MediaPlayer.OnVideoSizeChangedListener() { // ->(6) @Override public void onVideoSizeChanged(MediaPlayer mediaPlayer, int w, int h) { int winWidth = getView().getWidth(); int winHeight = getView().getHeight(); if (w > h || (w == h && winWidth < winHeight)) { int width = winWidth; float p = (float) h / (float) w; int height = (int) ((float) width * p); mSurfaceHolder.setFixedSize(width, height); } else { int height = winHeight; float p = (float) w / (float) h; int width = (int) ((float) height * p); mSurfaceHolder.setFixedSize(width, height); } } }); mMediaPlayer.setDataSource(mPlayingUri); mMediaPlayer.setDisplay(mSurfaceHolder); // ->(7) mMediaPlayer.prepare(); // ->(8) mMediaPlayer.start(); mSeekBar.setMax(mMediaPlayer.getDuration()); // ->(9) .. 省略
(6) 動画のサイズに応じてSurfaceViewのサイズを変更させるためのコールバック追加
動画のサイズの倍率に合わせてFragment内で最大化する
(7) SurfaceViewと紐づける
(8) 再生準備 開始
(9) シークバーの最大値を動画のサイズに設定する
再生中動画の状態を取得してUIを更新していく処理を実装する
別スレッドを立てて監視をする
.. 省略 public class MoviePlayerFragment extends Fragment implements Runnable { // ->(10) .. 省略 private boolean mRunning; private Thread mPlayerThread; private final DecimalFormat mFormatterMin = new DecimalFormat("#"); private final DecimalFormat mFormatterSec = new DecimalFormat("00");agment.java .. 省略 mRunning = true; // ->(11) mPlayerThread = new Thread(this); mPlayerThread.start(); .. 省略 public void run() { while(mRunning) { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } mHandler.sendMessage(Message.obtain()); } } public void stopRunning() { mRunning = false; } private Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { if (mMediaPlayer == null) { return; } mSeekBar.setProgress(mMediaPlayer.getCurrentPosition()); // ->(12) String playButtonText = mMediaPlayer.isPlaying() ? "停止" : "再生"; // ->(13) mPlayPauseButton.setText(playButtonText); String loopButtonText = mMediaPlayer.isLooping() ? "連続" : "一回"; mLoopButton.setText(loopButtonText); int leftTime = mMediaPlayer.getCurrentPosition()/1000; // ->(14) String leftMinText = mFormatterMin.format((int)(leftTime / 60)); String leftSecText = mFormatterSec.format((int)(leftTime % 60)); int rightTime = mMediaPlayer.getDuration()/1000 - leftTime; String rightMinText = mFormatterMin.format((int)(rightTime / 60)); String rightSecText = mFormatterSec.format((int)(rightTime % 60)); mLeftTimeText.setText(leftMinText +":"+ leftSecText); mRightTimeText.setText("-"+rightMinText +":"+ rightSecText); } }; .. 省略
(10) Runnableをimplementしておく
(11) 別スレッドをスタートさせる
(12) シークバーの表示更新
(13) 再生ボタンの表示更新
(14) 再生時間の表示更新
シークバー操作の実装
.. 省略 mSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { @Override public void onProgressChanged(SeekBar seekBar, int i, boolean b) { // ->(15) if (null != mMediaPlayer) { if (!mMediaPlayer.isPlaying()) { mMediaPlayer.seekTo(i); } } } @Override public void onStartTrackingTouch(SeekBar seekBar) { // ->(16) if (null != mMediaPlayer) { mMediaPlayer.pause(); } } @Override public void onStopTrackingTouch(SeekBar seekBar) { // ->(17) if (null != mMediaPlayer) { mMediaPlayer.start(); } } }); .. 省略
トラッキング開始でMediaPlayerを一時停止してシークを開始して、トラッキングから指を話した時点で再生を再開する
一時停止中にトラッキングをすると再生開始してしまうがフラグ等で状態を管理する実装をするつもり
(15) プログレスが変更されたときの通知
(16) トラッキング開始時
(17) トラッキング終了時
サンプルコード
上記アプリ全体のサンブルコードはgithubにありますので参考にしていただければと思います。
https://github.com/rdme/MoviePlayerSample
Androidでカメラアプリを作成する(Android5.0以降に対応) (2)
Androidでカメラアプリを作成する(Android5.0以降に対応) (2)
撮影機能を実装する
カメラを準備するメソッドの修正
.. 省略 private Handler mBackgroundHandler; // ->(1) private ImageReader mImageReader; .. 省略 private void prepareCameraView() { CameraManager cameraManager = (CameraManager) mParentActivity.getSystemService(Context.CAMERA_SERVICE); try { String backCameraId = null; for (String cameraId : cameraManager.getCameraIdList()) { CameraCharacteristics characteristics = cameraManager.getCameraCharacteristics(cameraId); if (characteristics.get(CameraCharacteristics.LENS_FACING) == CameraCharacteristics.LENS_FACING_BACK) { backCameraId = cameraId; StreamConfigurationMap map = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); mPreviewSize = map.getOutputSizes(SurfaceTexture.class)[0]; Size jpegSize = getJPEGSize(characteristics); // ->(2) mImageReader = ImageReader.newInstance(jpegSize.getWidth(), // ->(3) jpegSize.getHeight(), ImageFormat.JPEG, 1); mImageReader.setOnImageAvailableListener(mReaderListener, mBackgroundHandler); // ->(4) } } .. 省略
(1) バックグラウンドハンドラをメンバ変数として定義する onResumeでスレッドを開始させておく(コードは省略)
(2) ストリーム設定からJPEGの出力サイズを取得する
(3) 画像を取得するためのImageReaderの初期化
(4) Image取得リスナーをセットする 実装内容は後述
ImageReaderとは
SurfaceTextureの画像にアクセスする機能を提供するクラス
撮影メソッドの実装
.. 省略 private void takePicture() { if(null == mCameraDevice) { return; } try { mCaptureRequestBuilder.addTarget(mImageReader.getSurface()); // ->(5) mCaptureRequestBuilder.set( // ->(6) CaptureRequest.CONTROL_MODE, CameraMetadata.CONTROL_MODE_AUTO); int rotation = mParentActivity.getWindowManager().getDefaultDisplay().getRotation(); // ->(7) mCaptureRequestBuilder.set( CaptureRequest.JPEG_ORIENTATION, ORIENTATIONS.get(rotation)); mCaptureSession.capture(mCaptureRequestBuilder.build(), // ->(8) new CameraCaptureSession.CaptureCallback() { @Override public void onCaptureCompleted(@NonNull CameraCaptureSession session, // ->(9) @NonNull CaptureRequest request, @NonNull TotalCaptureResult result) { super.onCaptureCompleted(session, request, result); } }, mBackgroundHandler); } catch (CameraAccessException e) { e.printStackTrace(); } } .. 省略
(5) 生成したImageReaderからSurfaceを取得する
(6) 自動モードをセットする
(7) 画像の縦横を設定する
(8) キャプチャを開始する
(9) キャプチャ成功時のコールバック
ImageReaderのコールバック
キャプチャが成功するとImageReadr.OnImageAvailableListenerに結果がかえってくる
.. 省略 private ImageReader.OnImageAvailableListener mReaderListener = new ImageReader.OnImageAvailableListener() { public void onImageAvailable(ImageReader imageReader) { // ->(10) Image image = imageReader.acquireNextImage(); // ->(11) ByteBuffer buffer = image.getPlanes()[0].getBuffer(); byte[] bytes = new byte[buffer.remaining()]; buffer.get(bytes); OutputStream output = null; String saveDirectory = Environment.getExternalStorageDirectory().toString(); String saveFileName = "pic_" + System.currentTimeMillis() + ".jpg"; try { File file = new File(saveDirectory,saveFileName); output = new FileOutputStream(file); output.write(bytes); } catch (IOException e) { e.printStackTrace(); } finally { if (image != null) { image.close(); } if (output != null) { try { output.close(); } catch (IOException e) { e.printStackTrace(); } } } String[] paths = {saveDirectory + "/" + saveFileName}; // ->(12) String[] mimeTypes = {"image/jpeg"}; MediaScannerConnection.scanFile(mParentActivity.getApplicationContext(), paths, mimeTypes, new MediaScannerConnection.OnScanCompletedListener() { @Override public void onScanCompleted(String s, Uri uri) { mParentActivity.runOnUiThread(new Runnable() { @Override public void run() { Toast.makeText(mParentActivity,"saved",Toast.LENGTH_LONG).show(); createCameraCaptureSession(); // ->(13) } }); } } ); } }; .. 省略
(10) 画像を取得可能になったときのコールバック
(11) バッファから画像をファイルとして保存していく処理
(12) MediaScannerConnectionでファイルをスキャンする これはメディアライブラリで使用可能にするための処理
(13) 再度プレビューを開始する
サンプルコード
githubに置いてありますので、参考にしていただければと思います。
Androidでカメラアプリを作成する(Android5.0以降に対応) (1)
Androidでカメラアプリを作成する(Android5.0以降に対応) (1)
主に使用するパッケージやクラス
android.hardware.camera2
Android5以上でカメラを使用するためのパッケージandroid.view.TextureView
カメラから取得した画像を連続描画するViewandroid.hardware.camera2.CameraDevice
端末毎のカメラオデバイス(前面カメラや背面カメラそれぞれ)android.hardware.camera2.CameraCaptureSession
カメラデバイスの画像を取得するセッション
開発環境
Android 5.0 ~
Android Studio 2.3.3
カメラを使用する流れ
- TextureViewをアクティブにする
- CameraManagerをContextから取得してCameraDeviceを起動する
- CameraDeviceからCameraCaptureSessionを作成する
- CameraCaptureSessionから取得した画像をTextureViewに描画していく
準備
Manifestに追加する
<uses-permission android:name="android.permission.CAMERA" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <uses-feature android:name="android.hardware.camera.autofocus" /> <uses-feature android:name="android.hardware.camera" /> <uses-feature android:name="android.hardware.camera2.full" />
CameraFragmentの実装
Viewの準備
.. 省略 @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.fragment_camera, container, false); mTextureView = (TextureView) view.findViewById(R.id.texture_view); mTextureView.setSurfaceTextureListener(mSurfaceTextureListener); // ->(1) Button button = (Button) view.findViewById(R.id.button_take_picture); button.setOnClickListener(mButtonOnCickListener); // ->(2) return view; } .. 省略
(1) TextureViewのステータス変更のコールバックを登録する(後述)
(2) 撮影ボタンのリスナ登録 (省略)
TextureViewのステータス変更のコールバック
.. 省略 private TextureView.SurfaceTextureListener mSurfaceTextureListener = new TextureView.SurfaceTextureListener() { @Override public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, int i, int i2) { // ->(3) prepareCameraView(); // ->(4) } @Override public void onSurfaceTextureSizeChanged(SurfaceTexture surfaceTexture, int i, int i2) { } // ->(3) @Override public boolean onSurfaceTextureDestroyed(SurfaceTexture surfaceTexture) { return false; } // ->(6) @Override public void onSurfaceTextureUpdated(SurfaceTexture surfaceTexture) { } // ->(7) }; .. 省略
TextureView.SurfaceTextureListenerを使用する
(3) TexutureViewの準備が完了
(4) CameraDeviceの準備を行う
(5) サイズ変更通知 今回は無視
(6) 破棄 今回は無視
(7) 更新 今回は無視
CameraDeviceの準備
.. 省略 private CameraDevice mCameraDevice; // ->(8) private Size mPreviewSize; .. 省略 private void prepareCameraView() { CameraManager cameraManager = (CameraManager) mParentActivity.getSystemService(Context.CAMERA_SERVICE); // ->(9) try { String backCameraId = null; for (String cameraId : cameraManager.getCameraIdList()) { // ->(10) CameraCharacteristics characteristics = cameraManager.getCameraCharacteristics(cameraId); if (characteristics.get(CameraCharacteristics.LENS_FACING) == CameraCharacteristics.LENS_FACING_BACK) { // ->(11) backCameraId = cameraId; StreamConfigurationMap map = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); // ->(12) mPreviewSize = map.getOutputSizes(SurfaceTexture.class)[0]; } } if (backCameraId == null) { return; } if (ActivityCompat.checkSelfPermission(mParentActivity, Manifest.permission.CAMERA) // ->(13) != PackageManager.PERMISSION_GRANTED) { return; } cameraManager.openCamera(backCameraId, new CameraDevice.StateCallback() { // ->(14) @Override public void onOpened(@NonNull CameraDevice cameraDevice) { // ->(15) mCameraDevice = cameraDevice; createCameraCaptureSession(); } @Override public void onDisconnected(@NonNull CameraDevice cameraDevice) { // ->(16) } @Override public void onError(@NonNull CameraDevice cameraDevice, int i) { // ->(17) } }, null); } catch (CameraAccessException e) { e.printStackTrace(); } } .. 省略
CameraManagerからカメラ情報を取得してカメラの準備をする
(8) メンバ変数にCameraDeviceを定義
(9) システムサービスのCameraManagerを取得する
(10) getCameraIdListで配列で端末の使用するカメラのIDを取得できる
(11) 背面カメラのIDを取得
(12) 背面カメラのプレビュー画面サイズを取得
(13) パーミッションの確認
(14) 背面カメラのIDを使用してカメラをアクティブにする
(15) 成功のコールバック CameraCaptureSessionを有効化する
(16) 接続終了
(17) 失敗
CameraCaptureSessionの有効化
.. 省略 private void createCameraCaptureSession() { if (null == mCameraDevice || !mTextureView.isAvailable() || null == mPreviewSize) { return; } SurfaceTexture texture = mTextureView.getSurfaceTexture(); // ->(18) if (null == texture) { return; } texture.setDefaultBufferSize(mPreviewSize.getWidth(),mPreviewSize.getHeight()); Surface surface = new Surface(texture); try { mCaptureRequestBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW); // ->(19) } catch (CameraAccessException e) { e.printStackTrace(); } mCaptureRequestBuilder.addTarget(surface); try { mCameraDevice.createCaptureSession(Arrays.asList(surface), new CameraCaptureSession.StateCallback() { // ->(20) @Override public void onConfigured(@NonNull CameraCaptureSession cameraCaptureSession) { // ->(21) mCaptureSession = cameraCaptureSession; updatePreview(); } @Override public void onConfigureFailed(@NonNull CameraCaptureSession cameraCaptureSession) { // ->(22) Toast.makeText(mParentActivity, "onConfigureFailed", Toast.LENGTH_LONG).show(); } }, null); } catch (CameraAccessException e) { e.printStackTrace(); } } .. 省略
CameraDeviceからキャプチャを取得するためのセッションを確立する
(18) サイズを設定してTextureViewからSurfaceViewを初期化する
(19) CameraDeviceからCaptureRequestを生成してプレビュー画面をターゲットに設定する
(20) SurfaceViewを使用してCameraDeviceとのセッションを確立する
(21) 確立成功 画面の更新を開始する
(22) 失敗
CaptureSessionから画像を繰り返し取得してTextureViewに表示する
.. 省略 private void updatePreview() { if (null == mCameraDevice) { return; } mCaptureRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE); // ->(23) HandlerThread thread = new HandlerThread("CameraCapture"); // ->(24) thread.start(); Handler backgroundHandler = new Handler(thread.getLooper()); try { mCaptureSession.setRepeatingRequest(mCaptureRequestBuilder.build(),null,backgroundHandler); // ->(25) } catch (CameraAccessException e) { e.printStackTrace(); } } .. 省略
CaptureSessionから画像を繰り返し取得してTextureViewに表示する
(23)オートフォーカスを有効にするための設定
(24)バックグラウンドで実行するためのスレッドを作成
(25)リクエストを開始する
以上がカメラを起動するためのコードです。
このフラグメントを表示する前にカメラのパーミッションをユーザに要求する必要があります。
カメラのパーミッションをリクエストする
今回は呼び出し元のMainActivityに実装します
.. 省略 private final int MY_CAMERA_REQUEST_CODE = 1001; .. 省略 private void showCameraFragment() { if (checkSelfPermission(Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) { // ->(26) requestPermissions(new String[]{Manifest.permission.CAMERA},MY_CAMERA_REQUEST_CODE); // ->(27) return; } CameraFragment f = new CameraFragment(); // ->(28) this.setFragment(f); } @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { // ->(29) if (requestCode != MY_CAMERA_REQUEST_CODE) { return; } if (grantResults[0] == PackageManager.PERMISSION_GRANTED) { // ->(30) CameraFragment f = new CameraFragment(); this.setFragment(f); } else { // ->(31) } } .. 省略
(26) すでに許可済みか判定する
(27) リクエスト開始
(28) 許可済みであればそのまま起動 (this.setFragmentの内容は省略)
(29) パーミッションのリクエスト結果のコールバックをOverrideしておく
(30) 成功 CameraFragmentを起動する
(31) 失敗
まとめ
以上でカメラを起動して画像を画面に写すという処理が完了しました。
実際に撮影して画像を保存する処理は後ほど実装していきます。
あと、画面の回転に対応していないのでそちらも後ほど実装していきます。
サンプルコード
githubにありますので参考にしていただければと思います
https://github.com/rdme/PictureSample
AndroidでWebブラウザアプリを作る
AndroidでWebブラウザアプリを作る
主に使用するクラス
android.webkit.WebView
WebView実装
...省略 private WebView mWebView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // webView mWebView = (WebView) findViewById(R.id.web_view); mWebView.setWebViewClient(new MyWebViewClient()); // ->(1) mWebView.loadUrl("https://www.yahoo.co.jp"); // ->(2) mWebView.getSettings().setJavaScriptEnabled(true); // ->(3) } ...省略
(1) setWebViewClientはWebViewでのイベントをハンドリングするために実装してsetする
(2) URLを読み込む
(3) WebView内でJavaScriptを有効にする
WebViewClientでWebView内のイベントをハンドリングする
...省略 private class MyWebViewClient extends WebViewClient { @Override public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) { // ->(4) return false; } @Override public void onPageStarted(WebView view, String url, Bitmap favicon) { // ->(5) super.onPageStarted(view, url, favicon); } @Override public void onPageFinished(WebView view, String url) { // ->(6) super.onPageFinished(view, url); } @Override public void onReceivedError(WebView view, WebResourceRequest request, WebResourceError error) { // ->(7) super.onReceivedError(view, request, error); } } ...省略
(4) リクエスト開始前にハンドリングされる trueを返すと他アプリやActivityが起動するデフォルトの挙動になるのでfalseを返す
(5) 読み込み開始処理
(6) 読み込み終了
ブラウザであればこの時点でURLのstringを取得してURLバーに表示させることができる
(7) エラーハンドリング
端末の戻るボタンでブラウザバックを実装
...省略 @Override public void onBackPressed() { if (mWebView.canGoBack()) { // ->(7) mWebView.goBack(); // ->(8) } else { super.onBackPressed(); // ->(9) } } ...省略
(7) canGoBackがtrueならばブラウザバックをすることができる
(8) goBackでブラウザバック
(9) canGoBackがfalseならば端末の戻る機能をそのまま使用する
検索を実装する
android.support.v7.widget.SearchView
を利用して検索バーを実装する
...省略 private SearchView mSearchView; ...省略 @Override protected void onCreate(Bundle savedInstanceState) { ...省略 // search view mSearchView = (SearchView) findViewById(R.id.search_view); mSearchView.setOnQueryTextListener(new MySearchViewTextListener()); // ->(10) ...省略
(10) イベントリスナをセットする
...省略 private class MySearchViewTextListener implements SearchView.OnQueryTextListener { // ->(11) @Override public boolean onQueryTextSubmit(String query) { // ->(12) searchTextInWebView(query); // ->(13) return false; // ->(14) } @Override public boolean onQueryTextChange(String newText) { // ->(15) return false; // ->(16) } } ...省略
(11) SearchView.OnQueryTextListenerをimplementsしたクラスを定義する
(12) キーボードの送信を押したときのコールバック
(13) WebViewを使用して検索するためのメソッド(後述)
(14) falseを返すとハンドリングする trueを返すとデフォルトの挙動になる(Intentの発生など)
(15) テキストが変更されたときのコールバック
(16) falseを返すとハンドリングする trueを返すとデフォルトの挙動になる(Intentの発生など)
...省略 /** WebView内でYahoo検索をするメソッド */ private void searchTextInWebView(String text) { String p = ""; // ->(17) try { p = URLEncoder.encode(text,"UTF-8"); // ->(18) } catch (UnsupportedEncodingException e) { e.printStackTrace(); } String url = "https://kids.yahoo.co.jp/search/bin/search?ei=UTF-8&fr=ush&p="+p; // ->(19) mWebView.loadUrl(url); mSearchView.clearFocus(); // ->(20) mWebView.requestFocus(); } ...省略
(17) 検索クエリの初期化
(18) java.net.URLEncoderを使用して検索クエリをエンコードする
(19) Yahoo検索のGETパラメータに検索クエリをセットする
(20) SearchViewのフォーカスを外してキーボードを隠す
まとめ
以上で簡単なブラウザアプリを実装することができます
サンプルコード
githubにありますので参考にしていただければと思います https://github.com/rdme/WebViewSample
Android端末のセンサーを扱う
Android端末のセンサーを扱う
使用可能なセンサーリストを取得する
SensorManagerを使用する
/* ActivityのインスタンスからSensorManagerを取得 */ SensorManager manager = (SensorManager) activity.getSystemService(Context.SENSOR_SERVICE); /* getSensorListの引数にSensor.TYPE_ALLでリストを取得 */ List<Sensor> sensors = manager.getSensorList(Sensor.TYPE_ALL); for (Sensor sensor : sensors) { Log.d("Sensor","name:" + sensor.getName() + " type:" + String.valueOf(sensor.getType())); }
↓実行結果 (Nexsus9 Android7.0)
name:Accelerometer Sensor type:1 name:Magnetic field Sensor type:2 name:Gyroscope Sensor type:4 name:CM32181 Light sensor type:5 name:Pressure Sensor type:6 name:CWGD Orientation Sensor type:3 name:Rotation Vector type:11 name:Linear Acceleration type:10 name:Gravity type:9 name:Magnetic Uncalibrated type:14 name:Gyroscope Uncalibrated type:16 name:Game Rotation Vector type:15 name:Geomagnetic Rotation Vector type:20 name:Significant Motion type:17 name:Step Detector type:18 name:Step Counter type:19 name:Accelerometer Sensor (WAKE_UP) type:1 name:Magnetic field Sensor (WAKE_UP) type:2 name:Gyroscope Sensor (WAKE_UP) type:4 name:Pressure Sensor (WAKE_UP) type:6 name:CWGD Orientation Sensor (WAKE_UP) type:3 name:Rotation Vector (WAKE_UP) type:11 name:Linear Acceleration (WAKE_UP) type:10 name:Gravity (WAKE_UP) type:9 name:Magnetic Uncalibrated (WAKE_UP) type:14 name:Gyroscope Uncalibrated (WAKE_UP) type:16 name:Game Rotation Vector (WAKE_UP) type:15 name:Geomagnetic Rotation Vector (WAKE_UP) type:20 name:Step Detector (WAKE_UP) type:18 name:Step Counter (WAKE_UP) type:19
Android7.0のNexsus9で検証
加速度(Acceleromenter)、磁場(Magnetic field)、ジャイロスコープ(Gyroscope)、光(CM32181 Light)、気圧(Pressure)、等のセンサーが使用できる
WAKE_UPというのはセンサー取得時にgetDefaultSensorの引数wakeUpのところにtrueを入れれはwake_upセンサーを取得できる
Sensorクラスについて
/*タイプ SensorManagerのgetDefaultSensorの引数に使用する*/ public int getType(){} /*センサーの名前を取得 */ public String getName(){}
getTypeで取得できるタイプはSensorクラスに定義されている
public static final int TYPE_ACCELEROMETER = 1;
public static final int TYPE_MAGNETIC_FIELD = 2;
public static final int TYPE_GYROSCOPE = 4;
等
センサーの名前は英語で取得できる(上記実行結果)
加速度センサーを使用して値を取得する流れ (Accelerometer Sensor)
/* ActivityのインスタンスからSensorManagerを取得 */ SensorManager manager = (SensorManager) activity.getSystemService(Context.SENSOR_SERVICE); /* 加速度センサーを取得 */ Sensor accelerometerSensor = manager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER); /* SensorManagerにリスナー、加速度センサーインスタンス、サンプリング回数を引数として、センサーを登録する */ /*(thisのクラスにSensorEventListenerをimplementsしておく)*/ manager.registerListener(this,accelerometerSensor,SensorManager.SENSOR_DELAY_NORMAL); /* センサーの値が更新されると以下メソッドに値が入ってくる */ @Override public void onSensorChanged(SensorEvent sensorEvent) { float[] values = sensorEvent.values; float x = values[0]; // values配列の0番目の値はX軸の加速度 単位(m/s^2) float y = values[1]; // values配列の1番目の値はY軸の加速度 単位(m/s^2) float z = values[2]; // values配列の2番目の値はZ軸の加速度 単位(m/s^2) }
加速度センサーのTYPEの値はSensor.TYPE_ACCELEROMETER
サンプリング回数はSensorManagerクラスに
SENSOR_DELAY_FASTEST = 0; (3-30ms)
SENSOR_DELAY_GAME = 1; (3-30ms)
SENSOR_DELAY_NORMAL = 3; (約200ms)
SENSOR_DELAY_UI = 2; (約60ms)
と定義されている
参考: http://masterka.seesaa.net/article/180418713.html
SensorManagerクラスについて
/*センサーリストを取得する*/ public List<Sensor> getSensorList(int type){} /*センサーインスタンスを取得する*/ public Sensor getDefaultSensor(int type){} /*センサーを登録する */ public boolean registerListener(SensorEventListener listener, Sensor sensor, int samplingPeriodUs){} /*センサーを登録解除する sensor引数なしならばlistenerの全てのセンサーの登録解除*/ public void unregisterListener(SensorEventListener listener, Sensor sensor){} /*リスナの定義 */ public interface SensorEventListener { /*センサー値変更時*/ /*var1.valuesで値の配列を取得*/ void onSensorChanged(SensorEvent var1); /*センサーの正確さ変更時*/ void onAccuracyChanged(Sensor var1, int var2); }
getSensorListの引数にはSensorクラスで定義されているSensor.TYPE_ALLを入れれば全センサーを取得できる
基本的にはActivityのonPauseでunregisterListenerを呼び出してセンサーの処理を止める
しかし、止めなければアプリがバックグラウンド状態でも値の取得が可能(端末を傾けたらアプリ内で処理を実行する等が可能)
サンプルプロジェクト
センサーを使用するサンプルプロジェクトをgithubに置いたので参考にしていただければと思います。
https://github.com/rdme/AndroidSensorSample
Androidアニメーションの基礎
Androidアニメーションの基礎
使用する主なクラス
android.animation.Animator
android.animation.ValueAnimator
android.animation.ObjectAnimator
ObjectAnimatorはValueAnimatorのサブクラス
ValueAnimatorはObjectAnimatorのサブクラス
+ Animator
↓
+ ValueAnimator
↓
* ObjectAnimator
Animator
ValueAnimatorやObjectAnimatorの基底クラスで状態変化のリスナなどの機能を持つ
ValueAnimator
アニメーション化された値を計算し、ターゲットオブジェクト上に設定するアニメーションを実行するための簡単なタイミングエンジンを提供するクラス
ObjectAnimator
ターゲットオブジェクトと値と実際にアニメーションプロパティをコンストラクタで受け取り、内部的に適切な関数を実行してアニメーションを実行するクラス
アニメーションに使用するプロパティ
相対的な位置変更: translationX & translationY
絶対的な位置変更: x, y
回転 :rotation, rotationX, rotationY
サイズ変更 :scaleX, scaleY
支点 :pivotX, pivotY
透明度 :alpha
実装してみる
透明度
透明からだんだん表示されるアニメーション
private Animator alphaAnimator(View target) { float fromAlpha = 0f; float toAlpha = 1f; ObjectAnimator animator = ObjectAnimator.ofFloat(target,"alpha",fromAlpha,toAlpha); // ->(1) animator.setDuration(1000); // ->(2) return animator; }
(1) ObjectAnimator.ofFloatの引数は
+ アニメーションするターゲットオブジェクト
+ アニメーション前の値
+ アニメーション後の値
(2) animator.setDurationで1000msecかけてアニメーションするように設定している
回転
回転し続けるアニメーション
private Animator rotateAnimator(View target) { ObjectAnimator animator = ObjectAnimator.ofFloat(target, "rotation", 0f,360f); animator.setDuration(1000); animator.setRepeatCount(ValueAnimator.INFINITE); return animator; }
animator.setRepeatCountにINFINITEを指定して回転し続けるように指定している
移動
移動するアニメーション
private Animator translateAnimator(View target, float distance, float degree) { float fromX = 0f; float fromY = 0f; float toX = (float) (distance * Math.cos(Math.toRadians(degree))); float toY = (float) (distance * Math.sin(Math.toRadians(degree))); PropertyValuesHolder xHolder = PropertyValuesHolder.ofFloat("translationX",fromX,toX); // ->(3) PropertyValuesHolder yHolder = PropertyValuesHolder.ofFloat("translationY",fromY,toY); ObjectAnimator animator = ObjectAnimator.ofPropertyValuesHolder(target,xHolder,yHolder); animator.setDuration(1000); animator.setInterpolator(new BounceInterpolator()); // ->(4) return animator }
(3) Y軸の変更とX軸の変更を同時に行なっている
その場合はPropertyValuesHolderを使用して、ObjectAnimator.ofPropertyValuesHolderの引数に入れてAnimatorを初期化する
(4) animator.setInterpolatorでアニメーションカーブを設定することができる
+ AccelerateDecelerateInterpolator
なにも設定しなかった場合のデフォルトの挙動 加速したのちに減速してアニメーションを終える
+ DecelerateInterpolator
最初に加速してだんだん減速する
+ AccelerateInterpolator
だんだん加速する
+ BounceInterpolator
バウンドするように加速減速逆方向移動を繰り返して目標の値に到達する
+ OvershootInterpolator
目標の値を一旦オーバーしてから目的の値に到達する
全て組み合わせる
透明な状態から回転しながら目的の位置に移動する
private void alphaTranslateRotateAnimation(View target, float distance, float degree, long delay) { Animator alpha = alphaAnimator(target); Animator translate = translateAnimator(target,distance,degree); Animator rotate = rotateAnimator(target); AnimatorSet set = new AnimatorSet(); // ->(5) set.setStartDelay(delay); // ->(6) set.setInterpolator(new OvershootInterpolator()); set.playTogether(alpha,translate,rotate); // ->(7) set.start(); }
(5) AnimatorSetは複数のAnimatorを束ねて同時に実行したり順番に実行したりすることができるいわゆる管理クラス
Animatorを継承しているので上記説明した設定も行える
(6) setStartDelayでアニメーション実行開始時間の遅延時間を設定する
(7) playTogetherは引数に入れたAnimatorを同時に実行する設定
playSequentiallyで順番に実行させることもできる
サンプルプロジェクト
上記アニメーションを使用するサンプルプロジェクトをgithubに置いたので参考にしていただければと思います。
実装内容は、ボタンを押すと放射線状に他のボタンが現れるメニューボタンのようなアニメーションです
rdme/AnimationSample: Android Sample Application
参考
ObjectAnimator | Android Developers
素人からAndroidアニメーションの面白さを発見しよう - Qiita
AndroidでもiPhoneに負けないようなアニメーションを実装してみよう - Yahoo! JAPAN Tech Blog
SQLiteについて
SQLite
普段の開発ではCoreDataしかほとんど触ることがありません なのでSQLiteそのものについて調べてみる
特徴
コマンドラインで使ってみる
$ sqlite3 ~/Desktop/mydb.sqlite3 #データベースファイルを指定して起動する 存在しない場合
sqlite> create table human(name, age, perfect); #テーブル作成
splite> .tables #テーブル確認 human
splite> insert into human values('Suzuki',20,0); #インサート splite> insert into human values('Sato',24,0); #インサート splite> insert into human values('Nakata',29,1); #インサート splite> insert into human values('Hasegawa',24,0); #インサート
splite> .headers on #データ表示時にヘッダが追加される splite> .mode column #カラムの長さを合わせる splite> select * from human; #データ取得 name age perfect ---------- ---------- ---------- Suzuki 20 0 Sato 24 0 Nakata 29 1 Hasegawa 24 0 sqlite> select name from human where perfect = '1'; #データ取得 name ---------- Nakata sqlite> create index nameindex on human(name); #インデックス作成 sqlite> select perfect from human where name = 'Hasegawa'; #データ取得 perfect ---------- 0 sqlite> select perfect from human where name = 'Nakata'; #データ取得 perfect ---------- 1
- .importを使ってみる
insertdata.txtというファイルを用意しておく
inesrtdata.txtの中身
Suzuki|28|0 Sato|48|0 Nakata|19|1 Hasegawa|84|0
sqlite> drop table human; #いったんテーブルを削除 sqlite> create table human(name,age,perfect); #再度作成する sqlite> .import ./insertdata.txt human #ファイルからデータをインポート sqlite> select * from human; #データ取得 name age perfect ---------- ---------- ---------- Suzuki 28 0 Sato 48 0 Nakata 19 1 Hasegawa 84 0
splite> .exit #終了
感想
お手軽でした 次はRealmについて調べようと思います
参考
SQLiteの公式ホームベージ https://www.sqlite.org/ RealmとSQLiteのパフォーマンス検証を行った話 - Qiita
TCPとUDP
TCPとUDP
TCPとは
- Transmission Control Protocol
- インターネットにおいて標準的に利用されるプロトコル
UDPと比較して信頼性の高い通信を実現するために使用される
- シーケンス制御、再送制御
ポート番号とは
- 通信先のアプリケーションを指定するための番号
-
- 相手の応答があって初めて通信を開始する
- データ転送の前にコネクションの確立を行う
- TCPコネクションの確立に使用されるのが3ウェイハンドシェイクである
3ウェイハンドシェイク
- 3回のやりとりでコネクションを確立する
- 1- AからBへの接続確認
- 2- BからAへの確認応答と接続確認
- 3- AからBへの確認応答
- 3回のやりとりでコネクションを確立する
信頼性確保の方法
- シーケンス制御
- データを分割して送信する際にシーケンス番号を付与することで受信側で番号を元にデータを正しく組み立てることができる
- 再送制御
- 分割されたデータが到着したときに受信側から確認応答をすることで、時間内に確認応答がなかった場合にその番号のデータを再送信する
- シーケンス制御
通信効率向上
- ウィンドウ制御
- 受信側でウィンドウサイズを指定してそのサイズの分は確認応答を待つことなくデータを送信していく
- フロー制御
- 受信側で負荷が高まったときにウィンドウサイズを小さくして負荷がおさまるまで一定時間データ送信量を制御する
- ウィンドウ制御
UDPとは
- User Datagram Protocol
- 信頼性は高くないが、速さやリアルタイム性を求める通信に使用されるプロトコルである
- オーバーヘッドが少ない
- パケットロスしても再送をしない
- 用途
まとめ
CoronaSDK - 非同期で画像をダウンロードして表示する
CoronaSDK - 非同期で画像をダウンロードして表示する
display.loadRemoteImageを使います
Corona Docs — API | Libraries | display | loadRemoteImage
local halfW, halfH = display.contentCenterX,display.contentCenterY local function load_image_listener(ev) -- 1 if ev.isError then print 'load image error' end end -- load_image_listener local url = 'https://developer.coronalabs.com/demo/hello.png' local imageName = 'tmp.png' display.loadRemoteImage( url, -- 2 'GET', -- 3 load_image_listener, -- 4 imageName, -- 5 system.TemporaryDirectory, -- 6 halfW, -- 7 halfH )
- コールバックメソッド
- ダウンロードするurl
- HTTP Methodを指定
- コールバックメソッドを指定
- 保存する際の名前を指定
- 保存するフォルダを指定
- 画像の中心位置を指定 ここではdisplayの中心
これで画像が非同期でダウンロードされて、画面の中心に画像が表示されます
リストビュー(テーブルビュー)のセル一つ一つに画像を表示するには一手間かかります
詳しくは後ほど記事を書きますが、以下になんとなくの考え方だけ示しておきます
- 画像名をあらかじめinsertRowの時点でparamsにいれておく
- セルの表示タイミングでloadRemoteImageをする
- loadRemoteImageが完了したタイミングでtableView全体の再描画を行う
- それぞれのセルはtableView.view.rows[i]で取得することができる
- 再描画の際にrow.paramsにいれておいた画像名を使用してdisplay.newImageで画像をロードしてセルに表示する
Corona SDKでシンプルなTableView(List View)を実装する
Corona SDKでシンプルなTableView(List View)を実装する
2015-12-11
チュートリアル形式でコードを書いていきます
local widget = require 'widget' -- 1 local myList = widget.newTableView { -- 2 width = display.contentWidth, -- 3 height = display.contentHeight, -- 3 onRowRender = onRowRender, -- 4 onRowTouch = onRowTouch, -- 5 listener = scrollListener -- 6 }
- widgetライブラリをインポートしてインスタンス化
- widgetライブラリのnewTableViewでTableViewインスタンスを作成してディスプレイに表示
- widthとheightはdisplayサイズに合わせる
- Cellがレンダーされた時のリスナ登録 - 後述
- Cellがタップされた時のリスナ登録 - 後述
- TableViewがスクロールされた時のリスナ登録 - 後述
まだこれでは真っ白で何も表示されません
さらに以下を追記
local myData = {} -- 1 myData[1] = { name="Fred", phone="555-555-1234" } myData[2] = { name="Barney", phone="555-555-1235" } myData[3] = { name="Wilma", phone="555-555-1236" } myData[4] = { name="Betty", phone="555-555-1237" } myData[5] = { name="Pebbles", phone="555-555-1238" } myData[6] = { name="BamBam", phone="555-555-1239" } myData[7] = { name="Dino", phone="555-555-1240" } for i = 1, #myData do myList:insertRow{ -- 2 rowHeight = 60, isCategory = false, rowColor = { 1, 1, 1 }, lineColor = { 0.90, 0.90, 0.90 } } end
- myDataというtableインスタンスを作成(tableviewとは関係ないよ) -> https://docs.coronalabs.com/daily/api/library/table/index.html
- TableViewのメソッドTableView:insertRowでセルを挿入 isCategoryはiOSでいうそのセルをtableHeaderにするかどうか
これでセルの間の線が表示されてセルが作成されたことがわかります
しかしまだセルの内容がありません
そこでさっき登録したonRowRenderを実装します
その前に少し上のコードを修正します
for i = 1, #myData do myList:insertRow{ -- 2 rowHeight = 60, isCategory = false, rowColor = { 1, 1, 1 }, lineColor = { 0.90, 0.90, 0.90 }, params = { -- 1 name = myData[i].name, phone = myData[i].phone } } end
- insertRowの際にparamsというtableを設定することができます。そこにnameとphoneにパラメータをいれておきます
onRowRenderの実装です
注意する点としては先ほどのwidget.newTableviewでonRowRenderを登録するよりも上に下記コードを書いてください
local function onRowRender( event ) local row = event.row local id = row.index row.bg = display.newRect( 0, 0, display.contentWidth, 59 ) row.bg.anchorX = 0 row.bg.anchorY = 0 row.bg:setFillColor( 1 ) row:insert( row.bg ) if event.row.params then local name = event.row.params.name local phone = event.row.params.phone row.nameText = display.newText(name, 12, 0, native.systemFontBold, 18 ) row.nameText.anchorX = 0 row.nameText.anchorY = 0.5 row.nameText:setFillColor( 0 ) row.nameText.y = 20 row.nameText.x = 42 row.phoneText = display.newText(phone, 12, 0, native.systemFont, 18 ) row.phoneText.anchorX = 0 row.phoneText.anchorY = 0.5 row.phoneText:setFillColor( 0.5 ) row.phoneText.y = 40 row.phoneText.x = 42 row:insert( row.nameText ) row:insert( row.phoneText ) end return true end -- onRowRender
- event.rowでどのセルがレンダーに入ったかを取得できます
- event.row.paramsでinsertRowした際のparamsを取得できます
これで一通りテーブルビュー(リストビュー)の表示ができたと思います
最後に一通りの実装したコードを載せておきます
local widget = require 'widget' local onRowTouch local scrollListener local myData = {} myData[1] = { name="Fred", phone="555-555-1234" } myData[2] = { name="Barney", phone="555-555-1235" } myData[3] = { name="Wilma", phone="555-555-1236" } myData[4] = { name="Betty", phone="555-555-1237" } myData[5] = { name="Pebbles", phone="555-555-1238" } myData[6] = { name="BamBam", phone="555-555-1239" } myData[7] = { name="Dino", phone="555-555-1240" } local function onRowRender( event ) local row = event.row local id = row.index row.bg = display.newRect( 0, 0, display.contentWidth, 59 ) row.bg.anchorX = 0 row.bg.anchorY = 0 row.bg:setFillColor( 1 ) row:insert( row.bg ) if event.row.params then local name = event.row.params.name local phone = event.row.params.phone row.nameText = display.newText(name, 12, 0, native.systemFontBold, 18 ) row.nameText.anchorX = 0 row.nameText.anchorY = 0.5 row.nameText:setFillColor( 0 ) row.nameText.y = 20 row.nameText.x = 42 row.phoneText = display.newText(phone, 12, 0, native.systemFont, 18 ) row.phoneText.anchorX = 0 row.phoneText.anchorY = 0.5 row.phoneText:setFillColor( 0.5 ) row.phoneText.y = 40 row.phoneText.x = 42 row:insert( row.nameText ) row:insert( row.phoneText ) end return true end -- onRowRender local myList = widget.newTableView { width = display.contentWidth, height = display.contentHeight, onRowRender = onRowRender, onRowTouch = onRowTouch, listener = scrollListener } for i = 1, #myData do myList:insertRow{ rowHeight = 60, isCategory = false, rowColor = { 1, 1, 1 }, lineColor = { 0.90, 0.90, 0.90 }, params = { name = myData[i].name, phone = myData[i].phone }, } end
参照
CoronaSDKでSocket IO
CoronaSDKでSocket IO
2015-12-08
もともとSocket.IOでチャットアプリを作っていたのですが、iOSやAndroidのクライアントは用意されていて簡単にできます
が、CoronaSDKやluaには専用のクライアントフレームワークみたいなのがなかったのでSocket.IOのコードとか読んで実装してみました
※Socket通信などの理解レベルが多少曖昧なので以下の記述で通信できることはできましたが、申し訳ないですがかなり中途半端で危うい情報であるということを念頭に置いてお読みください
SocketIOとは
Socket.IO
リアルタイムウェブアプリケーションフレームワーク
様々なリアルタイムWeb技術をラップしてシンプルなインターフェースでリアルタイムWebを使用できるようにしてくれてるやつです
CoronaSDKのOSSにWebSocketができるものがあったのでWebSocketを使用してSockt.IOに対応しようと思います
WebSocketとは
WebSocket - Wikipedia
コンピュータネットワーク用の通信規格(プロトコル)の1つ
低コストでリアルタイム双方向通信ができるプロトコル
クライアントからハンドシェイクと呼ばれる要求をサーバに送ると、WebSocket専用コネクションを確立してその時点で双方向通信を開始することができます
Home | DMC Documentation
このライブラリを使用して実装していきます
実装
ライブラリ組み込み dmccuskey/dmc-websockets
コネクションの確立
- handshake urlを作成してサーバーへリクエスト handshake url example : http://example.com:80/socket.io/1/?t=1234567890112
- レスボンスからsidを取り出してwsプロトコルurl作成 example : ws://example.com:80/socket.io/1/websocket/9adubaadfadf09andf0
- 上記をwebsocetライブラリにつっこんで通信開始
通信
- Messageの処理
- Messageを受信すると下記のようなStringになっています
- Messageの処理
'5::{name:honyarara,args:[{honya:honey}}'
5::という数字はMessage送信という意味なので5::の部分は切り取ってからjsonライブラリでdecodeすることでtableとして扱います
送信をするときにも下記の形式で送信します
- WebSocketライブラリによるソケット通信の概要のコード例
local WebSockets = require 'dmc_corona.dmc_websockets' -- dmc-websocketsライブラリインポート local function socket_handler(event) -- websocketsのイベントを受け取るfunction local ev_type = event.type if ev_type == ws.ONOPEN then print 'WebSocket on open' elseif ev_type == ws.ONMESSAGE then print 'WebSocket on message' local mes = event.message local mesType = mes.data:sub(1,1) if mesType == '5' then -- got message local mes_json_str = mes.data:sub(5) -- 5::の部分の切り取り local json_table = json.decode(mes_json_str) -- jsondecodeしてtableとして扱う local ev_name = json_table.name or 'unknown' -- event name local args = json_table.args[1] or {} -- args print ('name : '.. ev_name) print ('args : '.. tostring(args)) end -- mestype == 5 elseif ev_type == ws.ONCLOSE then print 'WebSocket on close' elseif ev_type == ws.ONERROR then print 'websockets on error' end -- if ev_type end -- socket_handler ws = WebSockets:new{ -- 通信開始 uri = s_url, } ws:addEventListener( ws.EVENT, socket_handler) -- リスナー登録
以上
CoronaSDKでもSocket.IOの通信ができました、わあい
参考
Client handshake returns error message "Transport unknown" · Issue #1577 · socketio/socket.io websocket example doesn't work once uploaded on mobile phone (iPhone6) · Issue #28 · dmccuskey/DMC-Corona-Library
Corona SDK(lua) の開発環境構築 Vim編
Corona SDK(lua) の開発環境構築 Vim編
2015-12-07
Vim編と書いていますが、Vim編しか書きません
私Vimmerなんで
しかし他のエディタでの環境構築方法にも通じるものはあると思います(適当)
とにかく自分がやったことの忘備録的なことを書くぞ書きます
あ、ちなみに私のマシンはMacです
最近のvimは最初からlua対応されていると思います (:versionってすると+lua/dynみたいのが入ってる)
- corona vimでググったらでてくるサイト
Corona SDK Meets Vim - CuteMachine
Vim Snippets for Corona SDK and Lua Development - CuteMachine
Plain Old Blogumentation: VimからCorona SDKのシミュレーターを起動する方法
上記を参考に構築しました
vimrcに追加した部分
(Syntastic,OpenBrowser,SurroundVim,NeoSnippetのPluginを使用)
"以下directoryにluaのsnippetファイルを入れておく let g:neosnippet#snippets_directory='~/.vim/mysnippets' "Syntasticでluaのsyntaxチェック let g:syntastic_enable_lua_checker = 1 autocmd vimrc FileType lua call s:lua_my_settings() function! s:lua_my_settings() " CoronaSimulatorをF3で起動する map <buffer><F3> :!open -a /Applications/CoronaSDK/Corona\ Simulator.app %:h/main.lua <CR><CR> " Openbrowserを使用して カーソル下の単語をCoronaDocks検索する nnoremap <buffer>gl :OpenBrowser https://cse.google.com/cse?cx=009283852522218786394%3Ag40gqt2m6rq&q=<c-r><c-w>+docs<cr> " Openbrowserを使用してカーソル下の単語をググる nnoremap <buffer>gs :OpenBrowserSearch<Space><c-r><c-w><space>corona<cr> " SurroundVimを使用してコードをコメントアウト let g:surround_{char2nr("-")} = "--[[ \r --]]" " 選択中に-でコメントアウト vmap <buffer>- S- endfunction
- vimrc補足
"以下directoryにluaのsnippetファイルを入れておく let g:neosnippet#snippets_directory='~/.vim/mysnippets'
これについては
http://cutemachine.com/corona-sdk-tutorial/vim-snippets-for-corona-sdk-and-lua-development/Corona-SDK-Lua-Vim-Snippets.9c96a9bf.snippets
これをダウンロードしてlua.snipにリネームして使いました
" CoronaSimulatorをF3で起動する map <buffer><F3> :!open -a /Applications/CoronaSDK/Corona\ Simulator.app %:h/main.lua <CR><CR>
これですが
Plain Old Blogumentation: VimからCorona SDKのシミュレーターを起動する方法
の方法だとvimの中でshellが起動してうざかったんで直接シミュレータでmain.luaを起動するようにしちゃってます
" SurroundVimを使用してコードをコメントアウト let g:surround_{char2nr("-")} = "--[[ \r --]]" " 選択中に-でコメントアウト vmap <buffer>- S-
ここらへんについては
--[[ print('へいへいへい') --]]
とやるとコメントアウトがされて
---[[ print('へいへいへい') --]]
と上のやつに-を追加するだけでコメントが外されます
・他にもUniteVimやVimfilerを駆使すればかなり便利に開発することができます
特にUniteVimは使いこなしましょう
その他補足
Corona SDKとは
Corona SDKとは
2015-12-07
最近Corona SDKにはまっているんで紹介します
- 公式サイト
Corona SDK | Corona Labs - マルチプラットフォームで動くアプリを開発できるSDK
- 基本無料 ストアアップロードも無料 課金すればObj-CやJavaのネイティブAPIにアクセスできたりオフラインビルドできるようになったりする
- Lua言語
- ビルドしてから超瞬間的にシュミレータで確認できる(実機ビルドは少し遅いcoronaのサーバに問い合わせたりしてる)
- 描画は基本的にOpenGLでされているのでひと昔前の激遅Android端末でもなかなか速い
- ゲーム開発用の機能が十分に揃っている
- ただ
- ググっても日本語での解説がほとんどない
ちょっとすごいと思ったのは以下の画面
サンプルプロジェクトをシュミレータでちょちょいとビルドしてみた画面なんですが
なんとiOS標準のUIパーツとかAndroid標準のUIパーツが再現されているんです
もちろんGL上で描画されているんで、AndroidでiOSの画面を再現できたり、その逆もできそう