[追記 2021/05/09 午後あたり]
- MDCard単体でスワイプできないかあれこれ試していましたが、なんか出来たのでこちらに情報を追記します。
コードの詳細の方はGitHub(mdcard_swipe.pyをxviフォルダにアップ済み)をご覧ください。
本当に試してんのか?という方のために実行結果を以下に添付しておきます。
[追記 2021/05/09 夕方あたり]
- カード分だけ繰り返しウィジェットを列挙するだけでなく、イテレータを回す分だけ生成するということもできたので改めて
# コードの方はGitHubにて(mdcard_swipe_autocreate.py)。
実行結果は以下になります。
みなさん、おはこんばんは。
GW君の勢いも大分後退してきましたが、いかがだったでしょうか。まだ、終わってないぞ!
という方もいらっしゃるかもしれませんね。私のGW君はとっくに置き手紙を残し、どこかに
消え去りました。おそらく、来年あたりに帰ってくるのですかね。
まぁそういうことは一旦置いておいて、GW企画も幸いにして第3弾を迎えることが出来ました!
ありがとうございます。誰に向かってお礼を言っているか分からないですが、とりあえず感謝を
述べておきます。
今日のお題は前回述べた通りで、Card篇となっています。これまでもいくつかのウィジェットを
扱ってきましたが、今日のテーマに関して言うと、とても協力な武器になり得るものです。GW企画
の3テーマで簡単なアプリが出来てしまうのではないでしょうか。RPGだと、中ボスくらいは倒せ
そうな勢いです。
まぁ、そんな嘘かホントか分からない話は置いておいてさっそく取り掛かることにします。
Card
最初のマテリアルデザインはさることながら、概要が記載されています。
Cards contain content and actions about a single subject.
あまり自信の精度の低い解釈を載せてしまうとダメなので、こちらも依頼をしてみます。
カードには、一つのテーマに関するコンテンツやアクションが含まれています。
あ、今回から「Google翻訳」君を取りやめて「DeepL」君に依頼先を変更しました。
だって、翻訳精度がいいって言うから...
とまぁ、どうでもよいことは置いておいて、こちらに関しては馴染みが深みかもしれません。
Googleの検索結果の横方向にコンテンツが並べられたり、Google Mapの店の表示に使われたりで
枚挙にいとまがありません。こんな以外なところにも使われているよ!というのがありましたら
教えて下さいね。
で、KivyMDからは次のようなクラスらを提供するよということも書かれています。
- MDCard
- MDCardSwipe
で、さらにInfoとして次のようなことも触れられています。
MDCard inherited from BoxLayout. You can use all parameters and attributes
of the BoxLayout class in the MDCard class.
MDCardはBoxLayoutから継承しています。MDCardクラスでは、BoxLayoutクラスのすべての
パラメータと属性を使用することができます。
結構大胆な翻訳をするなぁと思っていることは置いておいて、BoxLayoutクラスのように使える
らしいです。このあたりはこのあとのコードの方を見てみましょうか。
今日については少しコードの列挙が多いので、いつもと進行を変えコード → 結果というように
記載していきます。ではレッツゴ。
MDCard
from kivy.lang import Builder
from kivymd.app import MDApp
KV ='''
MDScreen:
MDCard:
size_hint: None, None
size: "280dp", "180dp"
pos_hint: {"center_x": .5, "center_y": .5}
'''
class TestCard(MDApp):
def build(self):
return Builder.load_string(KV)
TestCard().run()
うーん、実にシンプル。確かに、BoxLayoutでも使用できるsize_hintやpos_hintなどが
使われています。特に目新しいものはないので、一旦結果の方を見てみます。
結果
存在感がありますね。では、さくさく次に進んでみましょう。
MDCard(add content)
次はカードにコンテンツを注入したものになります。依存性注入(DI)ではありません。
from kivy.lang import Builder
from kivymd.app import MDApp
KV = '''
MDScreen:
MDCard:
orientation: "vertical"
padding: "8dp"
size_hint: None, None
size: "70dp", "45dp"
pos_hint: {"center_x": .5, "center_y": .5}
MDLabel:
text: "Title"
theme_text_color: "Secondary"
size_hint_y: None
height: self.texture_size[1]
MDSeparator:
height: "1dp"
MDLabel:
text: "Body"
'''
class TestCard(MDApp):
def build(self):
return Builder.load_string(KV)
TestCard().run()
さっきよりか、BoxLayoutの特徴が顕著ですね。orientationとか出るともう出ちゃって
ますねーと言いたくなります。やってよかった、Layout篇。なんだか良く分からんと言う方は
GW企画の中のLayout篇を一読願います。というかKivyのBoxLayoutのマニュアル見た方が
早いかも。
kivyMDチュートリアル其の什肆 Components - Layout篇
とくにここも目新しいものは少ないですが、sizeというプロパティがありますね。これは
見ての通りということと、マニュアルの記載がないので詳細は省略します。というか見ての
通りしかないですね。
あとは、いままでほったらかしにしてきたMDSeparatorに触れ込みたいと思います。仕様
としては以下になります。なぜかCardに紛れ込んで、記載されています。謎しかない。。
class kivymd.uix.card.MDSeparator(**kwargs)
A separator line.
color
Separator color in rgba format.
color is a ColorProperty and defaults to None.
on_orientation(self, *args)
ただ線を引くだけでなく、色も選べるのですね。あとは、on_orientationというメソッド?
というものもありますね。なんにせよ、思わぬ武器が手に入りました。
結果
さて、結果の方を見てみましょう。
ただカードを表示するだけでは面白みがないので、サイズを1/4ほど縮めます。
# いやそれでも面白くねーよというツッコミは受け付けておりません
小っさっ!と言いたくなるほどですね。これはダメです。ダメよ〜ダメダメ。
懐かしいギャグをしたところで、ダメということを強調しておきます。というのもMaterial-
-Designの仕様としてもこんな仕様は認めていません。NGパターンにもこんな例はあるはずも
なく、コントリビューターが見ていたら開いた口が塞がらない状態に陥りそうです。どうか
このページを見ていませんように。
意味はないかもしれませんが、念のため正規パターンも載せておきます。これはマニュアル
通りのコードです。
んー、ばっちし!
MDCardSwipe
こちらは、タイトルにある通りCardをスワイプしたり削除できたりするものになります。
使用方法と仕様が簡潔にマニュアルに記載されていますね。
To create a card with swipe-to-delete behavior,
you must create a new class that
inherits from the MDCardSwipe class:
どうやら、このカードを使う場合はクラス宣言とkv側の両方を書かないとダメらしいです。
具体的な使用方法はマニュアル通りですが、以下みたいです。
<SwipeToDeleteItem>:
size_hint_y: None
height: content.height
MDCardSwipeLayerBox:
MDCardSwipeFrontBox:
OneLineListItem:
id: content
text: root.text
_no_ripple_effect: True
class SwipeToDeleteItem(MDCardSwipe):
text = StringProperty()
クラス宣言から言うと、textプロパティはクラス側でもkv側でも用意した方が良い
ということですね。理由としては後述のコードを見てもらった方がいいかもです。
kv側というと、なんかこのパターン見たことあるぞ!という方はKivyMDマスターです。(何様だ)
そうです、そうなのです。Componentsのいきなり最初で洗礼を浴びせられたBackdrop篇です!
そんなの知らねーよという方はマニュアルを見直すか以下リンクを参照ください。
kivyMDチュートリアル其の漆 Components - Backdrop篇
そのときと同様に、フロント・バックレイヤーを2つ用意してフロントの方にコンテンツを
仕込みます。バックレイヤーの方は、この後でも出てきますが削除ボタンなどを配置する
こともあるようですね。とりあえず、良く分からんという方はこの形を覚えてもらえれば
と思います。
で、あれこれ言ってもどうしようもないのでさっそくコードに入っていきましょう。
なんの変哲もないですが、マニュアルそのままのコードです。
from kivy.lang import Builder
from kivy.properties import StringProperty
from kivymd.app import MDApp
from kivymd.uix.card import MDCardSwipe
KV = '''
<SwipeToDeleteItem>:
size_hint_y: None
height: content.height
MDCardSwipeLayerBox:
# Content under the card.
MDCardSwipeFrontBox:
# Content of card.
OneLineListItem:
id: content
text: root.text
_no_ripple_effect: True
MDScreen:
MDBoxLayout:
orientation: "vertical"
spacing: "10dp"
MDToolbar:
elevation: 10
title: "MDCardSwipe"
ScrollView:
scroll_timeout : 100
MDList:
id: md_list
padding: 0
'''
class SwipeToDeleteItem(MDCardSwipe):
'''Card with `swipe-to-delete` behavior.'''
text = StringProperty()
class TestCard(MDApp):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.screen = Builder.load_string(KV)
def build(self):
return self.screen
def on_start(self):
'''Creates a list of cards.'''
for i in range(20):
self.screen.ids.md_list.add_widget(
SwipeToDeleteItem(text=f"One-line item {i}")
)
TestCard().run()
import
特に触れなくても良いかと思っていましたが、新たな発見というか当たり前の話ですが
上記のSwipeTo~クラス内で、以下のようにクラスを使うときというか以下は継承を使用
するときですが、import文を追加する必要があります。すみません、言わずもがなという
ところかもです。
(略)
from kivymd.uix.card import MDCardSwipe
(略)
class SwipeToDeleteItem(MDCardSwipe):
'''Card with `swipe-to-delete` behavior.'''
(略)
kv側
本来ここも目新しいものは少なく、SwipeToDeleteItemのレイアウトも先述通りなので
取り上げる必要はないのですが、ポツンと見慣れないScrollViewが姿を現しています。
MDScreen:
MDBoxLayout:
(略)
ScrollView:
scroll_timeout : 100
(略)
このscroll_timeoutってなんだろうなと思いマニュアルも見ていたのですが、以下リンク
の解説がめちゃ×2分かりやすかったので参照しておきます。
# ありがとうございます、gotta_dive_into_pythonさん!
念のため公式マニュアル(Kivy)の定義も触れておきます。
Timeout allowed to trigger the scroll_distance, in milliseconds.
If the user has not moved scroll_distance within the timeout,
the scrolling will be disabled, and the touch event will go to the children.
scroll_timeout is a NumericProperty and defaults to 55 (milliseconds)
according to the default value in user configuration.
ScrollView
https://kivy.org/doc/stable/api-kivy.uix.scrollview.html
クラス側
SwipeToDeleteItemも先述の通りなので、再掲はしません。TestCardについては注目
するべき箇所は以下になります。
class TestCard(MDApp):
(略)
def on_start(self):
'''Creates a list of cards.'''
for i in range(20):
self.screen.ids.md_list.add_widget(
SwipeToDeleteItem(text=f"One-line item {i}")
)
まぁ、これも絶対見たことありそうですが再掲をしました。kv側で宣言をしたmd_listが
on_startメソッドで使用されています。今まで触ってこなかったですが、add_widgetに
ついてもマニュアルから仕様を再確認したいと思います。
add_widget(self, widget, index=0, canvas=None)
Add a new widget as a child of this widget.
Parameters widget: Widget
Widget to add to our list of children.
※ 他にもindexとかcanvasとかもありますが元バージョンではないということなので
省略しています
要はこれさえ記載されていれば何でも?ウィジェットを追加できるようですね。
コードの方でもカスタムクラスのSwipeToDeleteItemを生成してウィジェットを
20個分入れ込んでいます。
結果
ということでこれも結果を見てみましょう。
うーん、問題なさそうです。
思いきりスワイプをしても、現時点では削除が出来ません。
先述できていないところですが、以下のように記載を加えるとスワイプする方向を
変更できるようです。
<SwipeToDeleteItem>:
# By default, the parameter is "left"
anchor: "right"
MDCardSwipe(Removing)
コンテンツを残しておく前提であれば問題ないと思いますが、消去したい場合はどう
しましょうか。まぁ、こんな遠い言い回ししなくても答えは出てますけどね。そう、
公式マニュアルならね。
すみません、調子乗りました。。大人しくマニュアルから使用方法など抜粋します。
Removing an item using the type_swipe = "auto" parameter
The map provides the MDCardSwipe.on_swipe_complete event.
You can use this event to remove items from a list:
<SwipeToDeleteItem>:
on_swipe_complete: app.on_swipe_complete(root)
def on_swipe_complete(self, instance):
self.screen.ids.md_list.remove_widget(instance)
上記のように指定すれば良いとのことです。on_swipe_completeでのコールバックメソッドの
引数にrootウィジェットごと渡していますね。この辺はmd_listが持っているremove_widget
メソッドがごにょごにょやっているんだなと思うしかありません。
で、さっそくサンプルコードに入っていきたいですが、すべてコードを載せても重複している
ところばかりなので先程のcard_swipe.pyからの差分を載せたいと思います。どこを変更
すれば良いのか分かりやすくなるかと思われます。
(略)
KV = '''
<SwipeToDeleteItem>:
size_hint_y: None
height: content.height
+ type_swipe: "auto"
+ on_swipe_complete: app.on_swipe_complete(root)
(略)
def build(self):
return self.screen
+ def on_swipe_complete(self, instance):
+ self.screen.ids.md_list.remove_widget(instance)
def on_start(self):
for i in range(20):
self.screen.ids.md_list.add_widget(
SwipeToDeleteItem(text=f"One-line item {i}")
)
(略)
まぁ、これも先述の使用方法通りですね。1つ変わっているのはtype_swipeプロパティが
追加されていることくらいですかね。これは任意プロパティ?と思われるかもですが、どちらか
というとあった方がいいかもしれません。これをなくすと(type_swipe: "hand"に戻る)どう
なるかというと、スワイプを途中で止めてカードを離すと逆方向に戻ってしまいます。ちょっと、
動作としては不自然に見えます。
[補足]
変更部分は他でもありますが、コメントの削除とかなので差分として出すのは本質ではない
と思い、省略しています。ご了承のほどを。
結果
ではどうなるか見てみましょう。
初期画面は問題なさそうです。
スワイプする様子がないのは申し訳ないですが、スワイプしても問題なく消去できます。
上記画面としては忘備録に近い内容ですが、スワイプを途中で止めて下の方向にずらすと
カードが途中で止まってしまいます。これもスマホ・タブレットとかだとどうなるか、
興味がある点になりますね。
MDCardSwipe(Add content to the bottom layer of the card)
ちょっと、疲れてきたw
うーん、後少しなのでがんばりマッスル。
という冗談を挟みながらですが、先述したバックレイヤーのほうにウィジェットを追加する方法です。
ここでは削除ボタンが題材ですが、SNSに共有など色々選択肢があるところです。
使用方法は以下になります。
To add content to the bottom layer of the card, use the MDCardSwipeLayerBox class.
<SwipeToDeleteItem>:
MDCardSwipeLayerBox:
padding: "8dp"
MDIconButton:
icon: "trash-can"
pos_hint: {"center_y": .5}
on_release: app.remove_item(root)
うーん、簡単ですね。バックレイヤーにボタンを追加するだけですね。ボタンもGW企画で
触っているしでバッチリですね!知らないという方は以下リンクを参照!
kivyMDチュートリアル其の什伍 Components - Button篇
サンプルコードもcard_swipe_delete.pyから微小たる変更になります。
(略)
KV = '''
<SwipeToDeleteItem>:
size_hint_y: None
height: content.height
- type_swipe: "auto"
- on_swipe_complete: app.on_swipe_complete(root)
MDCardSwipeLayerBox:
padding: "8dp"
+ MDIconButton:
+ icon: "trash-can"
+ pos_hint: {"center_y": .5}
+ on_release: app.remove_item(root)
(略)
class TestCard(MDApp):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.screen = Builder.load_string(KV)
- def on_swipe_complete(self, instance):
+ def remove_item(self, instance):
self.screen.ids.md_list.remove_widget(instance)
(略)
必要がないということからtype_swipeなどプロパティがなくなっています。あとは
ボタンの追加も先述通りですね。最後の差分としては、メソッドのリネームだけ。。
以上になります。
結果
では、結果の方を見てみましょう。
初期画面は問題ありません。
スワイプしても途中で止まったりすることはありません。
アイコンボタンを見事に消去されます。止まったりする心配事をなくすという意味では
こちらの方がいいかもしれませんね。止まることがなければ選択基準はフラットになり
ますが。
Focus behavior
こちらについては体力の限界が見えてきたので(大げさ)、おまけということにしたいと
思います。コードの方も著作権がめんどくさいということでフリー画像に変更している
くらいで特にそれ以外は変更していません。詳細を確認したい方はGitHubの方にて。
→card_starbutton.py
結果
実行結果は以下になります。
動かすためには、なにがしか画像ファイルを用意する必要があります。ないと動作しません。
うーん、可愛い。猫はいつみても可愛いですね。
少し画面サイズが大きいのは、スマホサイズにすると途端に文字などが読みにくくなる
ためです。なのでスマホ基準に考えると、設計を注意しなければいけません。
あとは、Ripple behaviorが動作しなかったことも注意が必要です。
今後に期待ということでしょうか。それとも私が動作の仕方わかってないだけだったりして。
補足
最後のまとめに入る前に、全然マニュアル最下部の仕様に触れてなかったので、使用
したものについてはここで触れておきます。
class kivymd.uix.card.MDCard(**kwargs)
focus_behavior
Using focus when hovering over a card.
focus_behavior is a BooleanProperty and defaults to False.
ripple_behavior
Use ripple effect for card.
ripple_behavior is a BooleanProperty and defaults to False.
class kivymd.uix.card.MDCardSwipe(**kw)
on_swipe_complete
Called when a swipe of card is completed.
anchor
Anchoring screen edge for card. Available options are: ‘left’, ‘right’.
anchor is a OptionProperty and defaults to left.
type_swipe
Type of card opening when swipe. Shift the card to the edge or
to a set position max_opened_x. Available options are: ‘auto’, ‘hand’.
type_swipe is a OptionProperty and defaults to auto.
まとめ
んーーー、今日も長かったぁー!
というどうでもよいことは良くて、いかがだったでしょうか。
少し予定よりもページが長くなったので、読み応えがあろうかと思われます。
せっかく身についたのですから、これから積極的に使っていきたいですね。
ということで、今日はこれにて終わろうと思います!また次回にて。
次回はDialogの続きのDropdown Itemに入っていく予定です。
それでは、ごきげんよう。
参照
Components » Card
https://kivymd.readthedocs.io/en/latest/components/card/
ScrollView
https://kivy.org/doc/stable/api-kivy.uix.scrollview.html