App ShortcutsのStatic Shortcutsを実装してみる
App Shortcutsとは
Android 7.1(SDK 25)で追加されたショートカットを作成する機能です.
アプリを長押しすると,ショートカットの機能が出てきます.
吹き出しをタップすることで指定したintentを起動できます.
図1: ショートカット(左上)
ショートカットは先ほどの吹き出しを長押しすることで,ホーム画面にピン留めできます.
図2: ピン留めされたショートカット(左上)
App Shortcutsには二種類あります.
- Static Shortcuts:xmlで定義する静的なショートカット
- Dynamic Shortcuts:Shortcut Managerを利用する動的なショートカット
です.
実装
今回はStatic Shortcutsを作ります.
①AndroidManifest.xmlを定義する
ルートActivityにmeta-dataを追加します.@xml/shortcut
はショートカットを設定したxmlです.コードは②に載せています.
(略) <application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:supportsRtl="true" android:theme="@style/AppTheme"> <activity android:name=".MainActivity"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> <meta-data android:name="android.app.shortcuts" android:resource="@xml/shortcuts" /> </activity> <activity android:name=".ComposeActivity" /> </application> (略)
②shortcuts.xmlを定義する
<?xml version="1.0" encoding="utf-8"?> <shortcuts xmlns:android="http://schemas.android.com/apk/res/android"> <shortcut android:shortcutId="compose" android:enabled="true" <!-- falseにするとこのショートカットは無効化される --> android:icon="@drawable/icon" android:shortcutShortLabel="@string/compose_shortcut_short_label1" <!-- アプリ長押し時に出るラベル名 --> android:shortcutLongLabel="@string/compose_shortcut_long_label1" <!-- ピン留めしたショートカットのラベル名 --> android:shortcutDisabledMessage="@string/compose_disabled_message1"> <!-- 無効化されたショートカットをタップした時に出る文言 --> <intent android:action="android.intent.action.VIEW" android:targetPackage="com.example.a01.appshortcut" android:targetClass="com.example.a01.appshortcut.ComposeActivity" /> <categories android:name="android.shortcut.conversation" /> </shortcut> </shortcuts>
参考
EventBusを使って簡単にCallbackを実現する
EventBusとは
あるClassでのイベントを検知して,別のClassで何かしたい時ありませんか?
Androidには簡単にそんなCallbackを実現する仕組みがあります.それがEventBusです.
例えば,ショッピングアプリの商品ページでお気に入りボタンが押された時,未ログインならログインページに飛ばし,ログイン完了後,再び商品ページに戻してお気に入り登録処理をしたい,なんて時に使えます(^^)
今回はgreenrobotのEventBusを使いますが,EventBusの提供元は一つではないです.
こちらにはsquere社のEventBusも紹介されています. qiita.com
実装
以下のgithubに載っていますが,EventBusは3stepで実現できちゃいます. github.com
今回は「ボタンがクリックされるとMessageEventというイベントが投げられ,MainActivityにToastを表示する」アプリを実装します.
イベントを投げるActivityも受けるActivityも同じなんで有難味がないかもしれませんが,簡略化のためそうします.
①イベント定義 (MessageEvent.java)
まず,ボタンがクリックされたことを通知するイベントClassを作ります.
MessageEvent.java
public class MessageEvent { }
②イベントを監視する準備 (MainActivity.java)
次にイベントの監視を設定します.
通知を受け取りたいクラスのonStartにEventBus.getDefault().register(this);
と書き
立つ鳥跡を濁さず,onStopではEventBus.getDefault().unregister(this);
をして登録解除します.
③イベントを投げる (MainActivity.java)
最後に,イベントを発火したいタイミングでEventBus#getDefault#postします.
引数は発火したいイベントクラスです.
EventBus.getDefault().post(new MessageEvent());
MainActivity.java
import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.view.View; import android.widget.Button; import android.widget.Toast; import org.greenrobot.eventbus.EventBus; import org.greenrobot.eventbus.Subscribe; import org.greenrobot.eventbus.ThreadMode; public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Button button = (Button) findViewById(R.id.button); button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { EventBus.getDefault().post(new MessageEvent()); } }); } @Subscribe(threadMode = ThreadMode.MAIN) public void onMessageEvent(MessageEvent event) { Toast.makeText(this, "イベント発火", Toast.LENGTH_SHORT).show(); } @Override public void onStart() { super.onStart(); EventBus.getDefault().register(this); } @Override public void onStop() { super.onStop(); EventBus.getDefault().unregister(this); } }
build.gradleはこちら
compile 'org.greenrobot:eventbus:3.0.0'
簡単で便利なのでよかったら使ってみてくださーいノシ
Android N マルチウィンドウ非対応
Android O Developer Previewも出てきていますが,Android Nのマルチウィンドウを非対応にする方法を記したいと思います.
マルチウィンドウとは
Android 7.0 Nougatで提供されている機能です.
複数のアプリが画面を同時に共有できます。たとえば、ユーザーは画面を分割し、ウェブページを左側の画面に表示しながら、右側の画面でメールを作成することができます
非対応にする方法
デフォルト対応になっています.(しかし,正常動作しないことがある)
非対応にするときは
targetSdkVersionが24以上の場合
AndroidManifest.xmlのapplication要素,もしくはActiovity要素
にandroid:resizeableActivity="false"と記載する.
ただし,Android Nのマルチウインドウ対応について調べて驚いたところ - Qiitaによると
他のアプリからstartActivityForResultで起動された場合,容赦無くマルチウィンドウの状態を引き継ぐので注意が必要である.
targetSdkVersionが24未満の場合
AndroidManifestのActivityの要素でいずれかを設定する.(ことで非対応になるらしい)
D3.jsでヒートマップっぽいものを作ってみた
概要
地域データを扱うことが多いので,可視化の方法としてD3.jsを勉強しました. (可視化は手段なんで,RでもTablueauでも状況に合わせて使い分ければいいと思います)
D3.js(Data Driven Documents)は
ウェブブラウザで動的コンテンツを描画するJavaScriptライブラリ(Wikipediaより)
です.
dotinstallをざっと見とくと理解が早いと思います.
今回は以下の記事をベースに地図に「都道府県別 100万人あたりの常設映画館数」をプロットしてみました. qiita.com
完成図はこちら. 情報源:政府統計の総合窓口 GL01010101 (東京が一番かと思いきや福岡が多いんですね.スクリーンの数とか,映画館の来場回数とか,DVDやテレビなど配給方法の割合とか色々気になりますが,今回は関係ないので触れません)
準備
ツール
# ShapeかGeoJSONへの変換に利用する $ brew install gdal # GeoJSONからTopoJSONへの変換に利用する $ npm install -g topojson # 最新版のV2系では,Qiitaで使用されているtopojsonというコマンドが4つのコマンドに分割されている. # V2ではgeo2topoが該当するが,今回はV1を指定してインストールする $ npm install -g topojson@1.6.27 # kokoじゃなくてもいいんだけど # 今回,外部ファイルからデータを読み込むんで,直接htmlファイルを開くだけだと外部ファイルを読み込めなず # サーバにファイルを置いてアクセスする必要がある.kokoはそのための手段. $ brew install node $ npm install -g koko
データ
Natural Earthから都道府県レベルの地図データをダウンロードします.Shapeファイルなんで,GeoJSON -> TopoJSONと変換して使います.
# ne_10m_admin_1_states_provinces.shpというShapeファイルが入っているので,pref.jsonというGeoJSONに変換する $ ogr2ogr -f GeoJSON -where "adm0_a3 = 'JPN'" pref.json ne_10m_admin_1_states_provinces.shp # GeoJSONからjapan.jsonというTopoJSONに変換する $ topojson -p name -p name_local -p latitude -p longitude -o japan.json pref.json
実装
こんな感じです. ちなみに2017/02/19時点でD3.jsの最新版はv4なんですが,アニメーションを設定しようとするとエラーになったのでhttps://d3js.org/d3.v3.min.jsを指定しています.
<!DOCTYPE html> <html lang="ja"> <head> <meta charset="utr-8"> <title></title> <style type="text/css"> .pref { fill: #fff; stroke: #aaa; } </style> </head> <body> <script src="https://d3js.org/d3.v3.min.js"></script> <script src="http://d3js.org/topojson.v0.min.js"></script> <script> var w = 1300; var h = 960; var svg = d3.select("body") .append("svg") .attr("width", w) .attr("height", h); // 地図の投影図法を設定する. var projection = d3.geo.mercator() .center([136, 35.5]) .scale(2000) .translate([w / 2, h / 2]); // GeoJSONからpath要素を作る. var path = d3.geo.path() .projection(projection); var pref = { "1":"北海道", "2":"青森県", "3":"岩手県", "4":"宮城県", "5":"秋田県", "6":"山形県", "7":"福島県", "8":"茨城県", "9":"栃木県", "10":"群馬県", "11":"埼玉県", "12":"千葉県", "13":"東京都", "14":"神奈川県", "15":"新潟県", "16":"富山県", "17":"石川県", "18":"福井県", "19":"山梨県", "20":"長野県", "21":"岐阜県", "22":"静岡県", "23":"愛知県", "24":"三重県", "25":"滋賀県", "26":"京都府", "27":"大阪府", "28":"兵庫県", "29":"奈良県", "30":"和歌山県", "31":"鳥取県", "32":"島根県", "33":"岡山県", "34":"広島県", "35":"山口県", "36":"徳島県", "37":"香川県", "38":"愛媛県", "39":"高知県", "40":"福岡県", "41":"佐賀県", "42":"長崎県", "43":"熊本県", "44":"大分県", "45":"宮崎県", "46":"鹿児島県", "47":"沖縄県" }; // TopoJSONを読み込む. d3.json("japan.json", function(error, japan) { console.log(japan); var topo = topojson.object(japan, japan.objects.pref).geometries; svg.selectAll(".pref") .data(topo) .enter() .append("path") // キーとバリューを逆にしてしまったので回避策. .attr("class", function(d) { var id = Object.keys(pref).filter((key) => { return pref[key] === d.properties.name_local }); return "pref pref" + id; }) .attr("d", path); }); var category = [{"name":"常設映画館数(人口100万人当たり)(館)", "data":{"1":10.4, "2":13.6, "3":14.0, "4":6.0, "5":13.5, "6":9.7, "7":7.2, "8":10.3, "9":8.1, "10":10.6, "11":4.1, "12":5.6, "13":21.4, "14":6.5, "15":5.6, "16":4.7, "17":6.9, "18":16.5, "19":5.9, "20":13.3, "21":6.4, "22":10.8, "23":7.5, "24":13.7, "25":9.2, "26":7.7, "27":6.6, "28":13.7, "29":3.6, "30":8.2, "31":24.4, "32":5.7, "33":5.2, "34":20.1, "35":13.5, "36":6.5, "37":18.3, "38":15.1, "39":13.6, "40":36.7, "41":6.0, "42":20.2, "43":31.2, "44":12.0, "45":12.6, "46":7.8, "47":9.1}}]; var max = 40; var hoge = Math.floor(255 / 40); // 右上にあった常設映画館数というテキストをマウスオーバーすると // 映画館数によって都道府県の色が濃くなるよう施す. svg.selectAll("text") .data(category) .enter() .append("text") .attr("class", "category-label") .attr("x", w - 360) .attr("y", function(d, i) { return i * 20 + 200; }) .text(function(d) { return d.name; }) .on("mouseover", function(d) { for (var key in d.data) { var color = 255 - hoge * d.data[key]; if (color < 0) { color = 0; } d3.select(".pref" + key) .transition() .style("fill", "rgb(" + color + ", " + color + " ,255)"); } d3.select(this).style("fill", "#66f"); }) .on("mouseout", function(d) { for (var i = 1; i < 48; i++) { d3.select(".pref" + i) .transition() .style("fill", "#fff"); } d3.select(this).style("fill", "gray"); }); </script> </body> </html>
動作確認
$ koko -o
参考URL
PHPで実践する動的計画法
※Qiitaに”動的計画法とメモ化”を説明する記事を書きましたので,こちらにも記載したいと思います.内容は全く同じです(>_<) qiita.com
動的計画法とメモ化
動的計画法(Dynamic Programming)とメモ化(Memorization)は,競技プログラミングなんかでよく使われるアルゴリズムの一緒です. 両者とも計算結果を保存し再利用することで,重複した計算による時間ロスを防ぐものです.
例を示しながら説明したいと思います.
動的計画法 or メモ化を使う動機
まず,フィボナッチ数列とは
n 番目のフィボナッチ数を Fn で表すと、Fn は再帰的に F0 = 0, F1 = 1, F(n + 2) = Fn + (Fn + 1) (n ≧ 0)
で定義される数列です.(Wikipediaより)
標準入力から入力される数字を n とし n 番目(0番目スタート)のフィボナッチ数を求めたい場合どう実装するでしょうか.
(色々な方法はあると思いますが)数学的に一般項を求めようとせず,定義をそのまま使うと,こんな感じの再帰的な処理が書けると思います.
<?php fscanf(STDIN, "%d", $n); echo fib($n) . "\n"; function fib($n) { if ($n == 0) return 0; if ($n == 1) return 1; return fib($n - 1) + fib($n - 2); }
しかし,これだと尋常じゃない時間がかかります. n=4でもこれだけ再帰的にfib関数が呼ばれるんです↓
fib(4) = fib(3) + fib(2) = (fib(2) + fib(1)) + (fib(1) + fib(0))
この時間を短縮しくてれるのが動的計画法 or メモ化です! 使いたくなりませんか?w では,実際にコードへ適用していきます.
メモ化でフィボナッチ数を求める
ポイントは$memoList
という配列に計算結果を保存しておき,余計な計算を減らすことです.
n 番目から計算するトップダウンなイメージです.
コードにするとこんな感じです.
<?php fscanf(STDIN, "%d", $n); echo fib($n) . "\n"; $memoList; function fib($n) { global $memoList; if (isset($memoList[$n])) return $memoList[$n]; // 保存済みならその結果を使う if ($n == 0) return 0; if ($n == 1) return 1; $memoList[$n] = fib($n - 1) + fib($n - 2); // 新たに計算した結果を保存する return fib($n - 1) + fib($n - 2); }
動的計画法でフィボナッチ数を求める
ポイントは$dp
という配列で,計算結果を保存するという点ではメモ化と一緒です.
ただ,こちらはメモ化とは逆向きに,0番目から計算するボトムアップなイメージです.
だから,関数は一度呼ぶだけです.
<?php fscanf(STDIN, "%d", $n); echo fib($n) . "\n"; function fib($n) { $dp[0] = 0; $dp[1] = 1; for ($i = 2; $i <= $n; $i++) { // ひたすら計算し,結果を配列に保存していく $dp[$i] = $dp[$i - 1] + $dp[$i - 2]; } return $dp[$n]; }
使い分け
• 計算の重複が起きる心配がないか、気にならないほど頻度が低い場 合は、素朴な再帰呼び出しで十分。
• 計算に必要な値がとびとびの場合は、メモ化が計算時間を節約でき て良い。
• 計算に必要な値がぎちぎちの場合は、動的計画法が計算時間を節約 できて良い。
(「アルゴリズム」資料 18. 動的計画法とメモ化より) とのことです.
参考文献
いきなりRxAndroid
RxAndroidを使う機会があったので,調べたことをまとめました.
※理解が浅いので間違っている箇所はご指摘いただけると嬉しいです><
RxAndroidとは
Reactive Extentions Androidの略で,RxJavaを内包しAndroidで利用するための機能を追加してあります.
ソースコードはこちらです.
非同期処理が簡単に実装できます.
AsyncTaskに比べ実装が簡単で,ModelとViewで役割分担しやすいです.
使い方
Observavle:処理を実行しObserverに結果を伝える
Observer:Observavleから結果を受け取り処理する
build.gradle
compile 'io.reactivex:rxjava:1.1.5' compile 'io.reactivex:rxandroid:1.2.0'
MainActivity.java
package himitsu; import android.os.Bundle; import android.support.v7.app.AppCompatActivity; import android.util.Log; import rx.Observable; import rx.Observer; import rx.Subscriber; import rx.schedulers.Schedulers; import rx.android.schedulers.AndroidSchedulers; public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Observable .create(new Observable.OnSubscribe<Integer>() { @Override public void call(Subscriber<? super Integer> subscriber) { // 重い処理 int i = 0; Log.d("debug", "Observable:onNext1"); subscriber.onNext(i); Log.d("debug", "Observable:onNext2"); Log.d("debug", "Observable:onCompleted1"); subscriber.onCompleted(); Log.d("debug", "Observable:onCompleted2"); } }) .subscribeOn(Schedulers.newThread()) // どのスレッドでObservableを走らせるか .observeOn(AndroidSchedulers.mainThread()) // どのスレッドでObserverを走らせるか .subscribe(new Observer<Integer>() { // Observable.subscribe(observer)すると処理が始まる @Override public void onCompleted() { Log.d("debug", "Observer::onCompleted"); } @Override public void onError(Throwable e) { Log.d("debug", "Observer::onError"); } @Override public void onNext(Integer i) { Log.d("debug", "Observer::onNext"); } }); } }
Android Monitor
debug: Observable:onNext1 debug: Observable:onNext2 debug: Observable:onCompleted1 debug: Observable:onCompleted2 debug: Observer::onNext debug: Observer::onCompleted
参考
kirimin.hatenablog.com
使い方やメソッドを丁寧に解説されていて,初心者の私にも分かりやすかったです.
qiita.com
AsyncTaskとの比較が簡潔に記されていました.
[Android] 画像・動画ファイルをアルバムから削除する
サンプルコード
import java.io.File; import android.content.Context; String outputPath = <ファイルのパス>; Context context = this; File file = new File(outputPath); if (file.exists()) { file.delete(); // ファイル自体の削除 context.getContentResolver().delete( MediaStore.Files.getContentUri("external"), MediaStore.Files.FileColumns.DATA + "=?", new String[]{outputPath} ); // アルバムからの削除 }
説明
ContentProviderはAndroidで他アプリのDBにあるデータを操作できるコンポーネントです. このContentProviderはContentResolverインスタンスで利用できます.このインスタンスはContextクラスのgetContentResolverメソッドで取得できます. 今回はContentResolverのdelete(Uri url, String where, String[] selectionArgs)メソッドで対象の画像・動画を削除しています. MediaStore.Files.getContentUri("external")はファイル一般を全部取得できるURIです.