20210917のAndroidに関する記事は4件です。

【Flutter】ListViewでD&Dソートに対応しつつ並び順を保存する

はじめに Flutterだとドラッグアンドドロップで並べ替え可能なListView自体はかなり楽に実装できるのですが、ネイティブ同様(?)並び順の保存に関しては自前で作る必要があります(たぶん)。 もし適当に保存しようものなら並べ替えやアイテムの追加・削除でごちゃごちゃになってしまいます(経験済み)。 なんとなく一番簡単そうな方法を書き綴っておきますので良ければ参考にしてみてください(もっといい方法があればコメント頂けると嬉しいです)。 因みにFlutterは始めたばかりなのでおかしなところがあるかも知れません。 それとListView自体の実装とSQL関連の詳細説明は省略しますので適宜調べて貰えると幸いです。 前提 ListViewに並べるアイテムはDBに実装済みのものとします。 今回は以下のアイテムを用意して進めます。 item.dart class Item { var id; var title; var sort; //その他省略 } 実装 ListViewの実装 先ずはD&Dソート可能なListViewを実装します。 Main.dart class _MainPageState extends State<MainPageState> { List<Item> itemList = []; @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text("Sample"),), body: FutureBuilder( future: _createList(), builder: (BuildContext context, AsyncSnapshot<List<Item>> snapshot){ if (!snapshot.hasData) { return Text("Loading..."); }else{ return ReorderableListView.builder(//ReorderableListViewにすることでD&Dソート可能 padding: EdgeInsets.all(10), itemCount: itemList.length, onReorder: (oldIndex, newIndex){//onReorderを追加、D&Dすると呼ばれる if (oldIndex < newIndex) { newIndex -= 1; } Item movedItem = itemList.removeAt(oldIndex);//アイテムの場所を入れ替える itemList.insert(newIndex, movedItem); }, itemBuilder: (context, index){ return _buildListItem(itemList[index], index); }, ); } }, ), ); } Widget _buildListItem(Item item, int index){ return Container( key: Key(item.id),//ソートに必要な一意のキー(なんでもいい) child: Card( child: ListTile( onTap: (){}, //onLongPress: (){},//無効化する title: Text(item.title), trailing: Text("$index/${item.sort}"),//インデックスと保存してある並び順 ), ), ); } Future<List<Item>> _createList() async { itemList = await DBProvider.db.getAllItem();//DBからとってくる itemList.sort((a,b) => a.sort.compareTo(b.sort));//保存済みの順番でソート return itemList; } } これでドラッグアンドドロップによるソートが可能なListViewが実装できました。 ポイントはListView.builderからReorderableListView.builderに変更すること、onReorderを追加すること、ListViewに並べるWidgetに一意のキーを持たせること、です。 ListViewに並べるWidgetの中身は特に何でもいいんですが分かりやすくする為に保存されている並び順とインデックスを表示してます。 次は並び順の保存を実装します。 並び順の保存 保存するタイミングは色々あると思いますが、今回はD&D時に毎回保存しようと思います。 先程のonReorder内に以下のメソッドを追加します。 main.dart class _MainPageState extends State<MainPageState> { //省略 onReorder: (oldIndex, newIndex){ //省略 _saveSort(); } _saveSort(){ itemList.asMap().forEach((index, item) {//asMap()でindexを持たせる item.sort = index.toString();//アイテムに保存する並び順を現在のインデックスに変える DBProvider.db.updateItem(item);//DBのアイテムを更新 }); } } これで_saveSort()が呼ばれるたびにD&D後のインデックスがDBに保存されると思います。 アイテムを追加したり削除したりしても並べ替え後のインデックスで上書きするので問題ないはずです(たぶん)。 ただ、これだと一つ問題というかUPDATE文が何回も呼ばれてしまうので、必要であればDBProviderを以下のように少し変更します。 db_provider.dart class DBProvider { //省略 updateItem(Item item) async {//こっちではなく final db = await database; var res = await db.update( tableName, item.toMap(), where: "id = ?", whereArgs: [item.id], ); return res; } updateAllItem(List<Item> itemList) async {//こっちを追加 final db = await database; var batch = db.batch(); itemList.forEach((item) { batch.update( tableName, item.toMap(), where: "id = ?", whereArgs: [item.id], ); }); var res = await batch.commit(); return res; } } main.dartも書き換えます。 main.dart _saveSort(){ itemList.asMap().forEach((index, item) {//asMap()でindexを持たせる item.sort = index.toString();//アイテムに保存する並び順を現在のインデックスに変える //DBProvider.db.updateItem(item);//これを削除して }); DBProvider.db.updateAllItem(item);//これを追加 } SQLあまり詳しくないのですがバッチ更新を行うことでパフォーマンスが良くなるみたいなので、データが多くなる場合はこのほうがいいっぽいです。 おしまい 問題があったり、もっといい方法があれば教えてください!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

THETA新機能バースト撮影方法

こんにちは。 @mShiiina です。 今回は、RICOH THETA Z1のファームェア V2.00.1 から搭載された新機能、バースト撮影について解説します。 バースト撮影とは まず、バースト撮影について説明します。 THETAにはもともとインターバル撮影という、連続して写真を撮影する機能があります。 インターバル撮影は一定間隔(2秒間隔以上)で撮影する機能であり、タイムラプス動画を撮影したいときなどに使える機能です。 バースト撮影は連続して写真を撮影するというところはインターバル撮影と同じですが、撮影間隔が最短で数十ミリ秒で可能な撮影です。 さて、バースト撮影はどんなときに有効な撮影方法なのでしょうか? よく使われるのは小さい子どもや動物など自由に動いてしまう被写体を撮影するときに、ベストショットを逃さないために撮影します。短い間隔でたくさん撮影することで、そのうちのどれかはいい写真になるはず!という心意気で撮影します。 バースト撮影の概要 バースト撮影について、api.ricohにドキュメントが追加されています。詳細は以下リンクから辿ってください。 https://api.ricoh/docs/theta-plugin-reference/camera-api/#burst-capture-mode 余談ですが、ドキュメント先頭にページ内リンクがついたので、目的の説明を探しやすくなっています。 簡単にこのAPIの特徴を咀嚼して記載しておくと、以下となります。 1枚から9枚まで奇数枚の指定を行えます。 撮影間隔は、シャッター速度が1/30より短い場合、最短で30ミリ秒くらいにできます。 枚数に9枚の上限があるのは、高速撮影を実現するためRAMにため込むのですが、RAM容量には限界があるからです。 奇数枚指定なのは、このAPIが、高速ブラケット撮影に主眼を置いて作られたためです。HDRIの元データ撮影に役立ちます。 データ形式はJPEGだけでなくDNGでも保存することができます。(ただしRAMの中に保存されています) THETAでの実装方法 バースト撮影のAPIは、WebAPIを提供していないため、スマホアプリ等から呼び出して使うことができません。 本体でのCameraAPI(こちらの記事などを参考)を用いて撮影するため、プラグインとして実装する必要があります。 実装したプラグインはContinuous Shooting プラグインとして提供しています。 解説のために、初版のソースコード一式を「サンプル」として以下に公開しておきます。 https://github.com/theta-skunkworks/theta-plugin-continuous-shooting-sample 今回はContinuous Shooting プラグインの実装を参考にプラグインでのバースト撮影の実装方法を説明します。 パラメータ設定 まず、バースト撮影であることをCameraAPIで指定します。 mParameters.set("RIC_SHOOTING_MODE", "RicStillCaptureStdBurst"); 撮影モードにバースト撮影であることを伝えます。 その他各種パラメータを設定します。(ソースはこちら) バースト撮影で指定することができる撮影パラメータは以下です。 パラメータ 意味 RIC_AEC_BURST_CAPTURE_NUM 何枚撮影するか RIC_AEC_BURST_BRACKET_STEP ブラケット撮影時のEV間隔の指定 RIC_AEC_BURST_COMPENSATION 露出補正の値 RIC_AEC_BURST_MAX_EXPOSURE_TIME 露出時間の上限 RIC_AEC_BURST_ENABLE_ISO_CONTROL ISO感度コントロールを有効にするか また、それぞれの値にはデフォルト値があるので、設定しなくても撮影可能です。 撮影 撮影はこれをたたくだけ。 mCamera.takePicture(onShutterCallback, null, onJpegPictureCallback); これまでのカメラAPIの使用方法と変わりませんね。 ファイル保存 CameraAPIを使用して撮影すると、撮影結果はCallbackで受信し、ファイル保存の処理を自前で実装する必要があります。 撮影時に指定した、onJpegPictureCallbackのインスタンスに対してCallbackされます。(ソースはこちら) ファイル保存はこれまでのやり方と変わりません。今回変わるのは、撮影された枚数分Callbackされるということです。 Continuous Shooting プラグインでは、Callbackを受け取ったときに指定した撮影枚数分Callbackが呼ばれていることを確認しています。 撮影が完了したことの管理が必要な場合は、正しく撮影回数分Callbackがきていることを確認してください。 おわりに いかがでしたでしょうか。 この記事を参考にして皆さんもバースト機能を使ったプラグインを開発いただけると嬉しいです。 単純な連写だけでなく高速ブラケット撮影ができますので、独自のHDRIプラグインを作成することに役立ちます。ぜひトライを! 参考までに、Continuous Shootingプラグインで撮影した時の9コマを使ってアニメGIFを作成してみました。 RICOH THETAプラグインパートナープログラムについて THETAプラグインをご存じない方はこちらをご覧ください。 パートナープログラムへの登録方法はこちらにもまとめてあります。 QiitaのRICOH THETAプラグイン開発者コミュニティ TOPページ「About」に便利な記事リンク集もあります。 興味を持たれた方はTwitterのフォローとTHETAプラグイン開発者コミュニティ(Slack)への参加もよろしくおねがいします。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

TWRPでError: 7が出るときに試してみる事

ERROR: 7 先ほど出たエラー名でググってみたものの情報が少なかったので(基本すぎて?)共有しようと思いました。 mkdir "/cache/recovery/2bdde85..../" failed: No space left on device E1001: Failed to update system image. Updater process ended with ERROR: 7 Error installing zip file '/sdcard/TWRP/lineage17.zip' 上記のエラーがlineageをtwrpでインストールしようとしたところ出ました。 ストレージに十分スペースは空いています。 解決策 cacheとdalvik-cacheをwipeしたのち再度インストールを試みたらエラーは出なくなりました。 環境 Lenovo Z5ProGT Lineage-17.1 TWRP-3.3.1-1004 最後に 今後同じエラーに遭遇した方の解決時間短縮に貢献できれば幸いです。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

rememberLazyListState の仕組み

疑問 次のコードは、表示されている要素のインデックスの中で最も小さいものをログに出力するコードです。出力されるタイミングは値が変わったとき、つまり一方向にゆっくりと 64dp スクロールしたときです。私の環境では 1dp = 2.75px であるため、176px 毎に出力されることになります。 data class Data(val id: Int) val data = (1..1000).map { Data(it) } @Composable fun MainScreen() { val state = rememberLazyListState() Log.e("TAG", state.firstVisibleItemIndex.toString()) LazyColumn(Modifier.fillMaxSize(), state = state) { items(data, key = { it.id }) { Box(Modifier.border(1.dp, Color.Black).fillMaxWidth().height(64.dp)) } } } それに対して次のコードは、1px スクロールするたびに出力されます。 data class Data(val id: Int) val data = (1..1000).map { Data(it) } @Composable fun MainScreen() { val state = rememberLazyListState() // ここだけを書き換えている Log.e("TAG", state.firstVisibleItemScrollOffset.toString()) LazyColumn(Modifier.fillMaxSize(), state = state) { items(data, key = { it.id }) { Box(Modifier.border(1.dp, Color.Black).fillMaxWidth().height(64.dp)) } } } 私の感覚では、state 変数のプロパティの一部だけを参照していようとも、1px スクロールする毎に firstVisibleItemScrollOffset の値が変わるのだから state 自身が変更されると思ってしまいます。しかし実際はもっと賢くて、firstVisibleItemIndex だけを参照しているときは 176px 毎に再描画が走ってくれます。firstVisibleItemScrollOffset が変更されているにも関わらず。 答え rememberLazyListState の実装を読むと答えは単純でした。この関数は State#value を返すのではなく、State#value をプロパティに持つ State#value でないオブジェクトを返していました。つまり、state.firstVisibleItemIndex や state.firstVisibleItemScrollOffset は State#value ですが、state は State#value ではありませんでした。 終わりに 参照しているプロパティが変更されたときだけ再描画が行われるので、「スクロールするたびに再描画が走るので rememberLazyListState を使うことは避けよう」と思わなくていいとわかり、安心しました。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む