【制作ログ】某アニメ風!赤黒デザインのPyQtタイマーアプリを作ってみた

プログラミング

🟢 どんなアプリを作ったのか?

「映画とかアニメで出てくる、あの“カウントダウンUI”ってかっこよくないですか?」

そんな気持ちから、某アニメ風のカウントダウンタイマーアプリをPython(PyQt)で作ってみました。

実際に作ったアプリのスクショがこちら👇

デザインのテーマは「赤 × 黒 × 未来感」+「文字は大きく&中央にドーンと配置」。

単なるストップウォッチやタイマーではなく、
**“見ていて楽しい、演出のあるタイマー”**を目指しました。

ちなみにこのタイマーアプリを作ろうと思ったきっかけは、すごくシンプルです。

  • 普段、**ポモドーロタイマー(25分作業+5分休憩)**を使って勉強していた
  • でも、だんだんと「自分専用のタイマーが欲しいな」と思うようになった

市販のアプリでも十分便利なんですが、
「せっかくなら見た目も好きなデザインで、自分で作ってみたい!」と思い立って、
今回の制作に踏み切りました。

🔸 主なポイントは以下の3つ:

  • 背景に画像を使ってUI全体を演出
  • 時間表示は縦長フォントで存在感UP
  • 残り10秒で点滅エフェクト、さらに起動時にも軽い演出あり!

日常での実用性というよりは、
**「ちょっとテンションの上がる演出タイマー」**という感覚で、遊び心を大切にしました。

🟡 アプリの仕様まとめ

ここでは、今回作ったタイマーアプリの機能・デザイン・こだわりポイントをざっくりまとめておきます。


🔧 アプリの基本仕様

項目内容
機能カウントダウンタイマー(スタート/ストップ/リセット)
UI構成背景画像+数字ラベル+操作ボタン(スタート・リセット)
タイマー動作秒単位でカウント、0秒になると自動停止
プリセットなし(時間は手動入力 or コードで固定)

🎨 デザインのこだわり

  • 背景に自作 or フリー画像を敷いて、UIの世界観を強調
  • 数字フォントは縦長・太字の読みやすいスタイルを選択
  • 配色は赤×黒をベースに、エッジの効いた印象

✨ 追加の演出・機能

  • 起動時にフェードイン演出を追加して、テンションを上げる
  • 残り10秒になると、数字が赤く点滅して“緊張感”を演出

🧰 使用技術・ライブラリ

使用技術用途
Pythonメイン言語
PyQt5GUI開発(ウィンドウ、ボタン、ラベルなど)
QTimer時間制御・点滅処理
QPropertyAnimationアニメーション処理(フェードインなど)

こうやって書き出してみると、機能はシンプルですが、
見た目・演出・世界観のバランスを取るのが意外と楽しい&難しい部分でした。

🟠 制作ステップ(ざっくり解説)

ここからは、タイマーアプリを作るまでの流れをざっくりと紹介します。


🔹① UIデザインからスタート

まずはPyQtのデザイナ感覚で、ウィンドウに要素を配置していくところから始めました。

  • 背景画像を設定し、ウィンドウ全体に貼り付け
  • 中央に大きなカウントダウン用の数字ラベルを配置
  • 下部にスタート/リセット用ボタンを設置

全体的に“未来っぽさ”と“非日常感”が出るように、
配置や余白にも気をつけました。

画像:UI初期デザイン


🔹② タイマーのロジックを実装

次に、PyQtのQTimerを使って、カウントダウンのロジックを組みました。

  • 秒単位で時間を減らして、画面上に表示
  • カウントが0になったらタイマー停止
  • リセットボタンで時間を初期値に戻す

特に「残り時間の表示更新」は、QTimerとの連携がカギになります。

pythonコピーする編集するself.timer = QTimer()
self.timer.timeout.connect(self.update_time)
self.timer.start(60) # 60msごと更新。これ以上早くすると処理が追い付かない

🔹③ アニメーション・演出の追加

基本の動作ができたら、最後は**“ちょっとカッコよく”演出を追加**!

  • アプリ起動時にフェードイン(透明 → 表示)
  • 0秒になったら数字が黄色く点滅

PyQtのQPropertyAnimation透明度や色の変化を制御しました。 GUIだけど動きがあるだけで、一気に“作品感”が出ます。


全体として、**「見た目 × 機能 × 演出」**を一体に作り込むのが今回の目標でした。
初めてやってみると、PyQtでも意外と色々できるなって感じました!

🟤 今回作成したコード(完成形)

作成したコード(クリック)
from PyQt5.QtWidgets import QApplication, QWidget, QLabel, QPushButton, QGraphicsOpacityEffect, QLineEdit
from PyQt5.QtGui import QPixmap, QFont
from PyQt5.QtCore import Qt, QTimer
import sys


class BaseScreen(QWidget):
    def set_background(self, image_path):
        self.bg_label = QLabel(self)
        self.bg_label.setPixmap(QPixmap(image_path))
        self.bg_label.setGeometry(0, 0, 800, 400)
        self.bg_label.setScaledContents(True)

    def create_button(self, text, x, y, width, height, callback, border_color="red"):
        btn = QPushButton(text, self)
        btn.setGeometry(x, y, width, height)
        btn.setStyleSheet(f"background: transparent; border: 2px solid {border_color}; color: white; font-size: 20px;")
        btn.clicked.connect(callback)
        return btn


class StartScreen(BaseScreen):
    def __init__(self, switch_callback):
        super().__init__()
        self.switch_callback = switch_callback
        self.initUI()

    def initUI(self):
        self.setWindowTitle("EVA Timer - Start")
        self.setGeometry(100, 100, 800, 400)
        self.set_background("Start画面.png")

        # フェードインエフェクト
        self.opacity_effect = QGraphicsOpacityEffect()
        self.bg_label.setGraphicsEffect(self.opacity_effect)
        self.opacity_effect.setOpacity(0.0)
        self.fade_timer = QTimer(self)
        self.fade_timer.timeout.connect(self.fade_in)
        self.fade_timer.start(50)

        # スタートボタン
        self.start_btn = self.create_button("Start", 350, 250, 100, 50, self.switch_callback, "white")

    def fade_in(self):
        opacity = self.opacity_effect.opacity()
        if opacity < 1.0:
            self.opacity_effect.setOpacity(opacity + 0.1)
        else:
            self.fade_timer.stop()


class TimerSetupScreen(BaseScreen):
    def __init__(self, switch_callback):
        super().__init__()
        self.switch_callback = switch_callback
        self.initUI()

    def initUI(self):
        self.setWindowTitle("EVA Timer - Setup")
        self.setGeometry(100, 100, 800, 400)
        self.set_background("Eva背景.png")

        # 数値入力
        self.input_field = QLineEdit(self)
        self.input_field.setGeometry(300, 150, 200, 50)
        self.input_field.setStyleSheet("font-size: 30px; text-align: center;")
        self.input_field.setPlaceholderText("最大999分")

        # 設定ボタン
        self.set_btn = self.create_button("設定", 350, 250, 100, 50, self.set_timer, "white")

    def set_timer(self):
        try:
            minutes = int(self.input_field.text())
            if 1 <= minutes <= 999:
                self.switch_callback(minutes)
        except ValueError:
            pass


class TimerScreen(BaseScreen):
    def __init__(self, minutes, switch_callback):
        super().__init__()
        self.initial_time = minutes * 60 * 1000
        self.time_remaining = self.initial_time
        self.switch_callback = switch_callback
        self.timer = QTimer(self)
        self.timer.timeout.connect(self.update_timer)
        self.flash_timer = QTimer(self)
        self.flash_timer.timeout.connect(self.toggle_flash)
        self.is_flashing = False
        self.initUI()

    def initUI(self):
        self.setWindowTitle("EVA Timer")
        self.setGeometry(100, 100, 800, 400)
        self.set_background("Eva背景.png")

        # タイマー表示
        font = QFont("DS-Digital", 50)
        font.setBold(True)
        self.label = QLabel(self.format_time(self.time_remaining), self)
        self.label.setFont(font)
        self.label.setStyleSheet("color: red; background: transparent;")
        self.label.setAlignment(Qt.AlignCenter)
        self.label.setGeometry(110, 150, 500, 120)

        # ボタン作成
        self.start_btn = self.create_button("", 150, 305, 120, 50, self.start_timer)
        self.stop_btn = self.create_button("", 256, 305, 113, 50, self.stop_timer)
        self.reset_btn = self.create_button("", 362, 305, 108, 50, self.reset_timer)
        self.config_btn = self.create_button("", 460, 305, 100, 50, self.switch_callback)

    def format_time(self, milliseconds):
        total_seconds = milliseconds // 1000
        minutes = total_seconds // 60
        seconds = total_seconds % 60
        hundredths = (milliseconds % 1000) // 10
        return f"{minutes:02}:{seconds:02}:{hundredths:02}"

    def update_timer(self):
        if self.time_remaining > 0:
            self.time_remaining -= 60
            self.label.setText(self.format_time(self.time_remaining))
        else:
            self.time_remaining = 0
            self.timer.stop()
            self.label.setStyleSheet("color: yellow; background: transparent;")
            self.flash_timer.start(500)

    def toggle_flash(self):
        color = "yellow" if self.is_flashing else "black"
        self.label.setStyleSheet(f"color: {color}; background: transparent;")
        self.is_flashing = not self.is_flashing

    def start_timer(self):
        if not self.timer.isActive():
            self.timer.start(60)

    def stop_timer(self):
        self.timer.stop()

    def reset_timer(self):
        self.timer.stop()
        self.flash_timer.stop()
        self.is_flashing = False
        self.label.setStyleSheet("color: red; background: transparent;")
        self.time_remaining = self.initial_time
        self.label.setText(self.format_time(self.time_remaining))


class AppController:
    def __init__(self):
        self.app = QApplication(sys.argv)
        self.start_screen = StartScreen(self.show_timer_setup)
        self.start_screen.show()

    def show_timer_setup(self):
        self.start_screen.close()
        self.timer_setup_screen = TimerSetupScreen(self.show_timer_screen)
        self.timer_setup_screen.show()

    def show_timer_screen(self, minutes):
        self.timer_setup_screen.close()
        self.timer_screen = TimerScreen(minutes, self.show_timer_setup)
        self.timer_screen.show()

    def run(self):
        sys.exit(self.app.exec_())


if __name__ == "__main__":
    controller = AppController()
    controller.run()

🟣 細かい技術ポイント解説(詳細版)


🔹 ① 起動時のフェードイン演出(QGraphicsOpacityEffect + QTimer)

📌 何をしているのか?

  • アプリを起動した瞬間、背景画像がふわっと現れるように見せる演出を実装
  • これは、UI全体を透明度0 → 1にゆっくり変化させることで実現している

🔧 どうやってる?

pythonコピーする編集するself.opacity_effect = QGraphicsOpacityEffect()
self.bg_label.setGraphicsEffect(self.opacity_effect)
self.opacity_effect.setOpacity(0.0)
  • QGraphicsOpacityEffect を背景画像に適用し、透明度を制御
  • QTimer を使って50msごとに opacity += 0.1 を加算

💡 参考コード

pythonコピーする編集するself.fade_timer = QTimer(self)
self.fade_timer.timeout.connect(self.fade_in)
self.fade_timer.start(50)
pythonコピーする編集するdef fade_in(self):
    opacity = self.opacity_effect.opacity()
    if opacity < 1.0:
        self.opacity_effect.setOpacity(opacity + 0.1)
    else:
        self.fade_timer.stop()

✅ ポイント

  • QGraphicsOpacityEffectPyQtでアニメーション表現を加えるための基本手法
  • この手法を応用すれば、ボタンやテキストのフェードインも可能

🔹 ② タイマーと点滅処理を分けたQTimerの使い方

📌 なぜタイマーが2つ必要?

  • self.timer:メインのカウントダウン処理(毎60ms)
  • self.flash_timer:残り0秒で数字を点滅させる処理(500msごとに色を交互に切り替え)

💡 flashの切り替え処理

pythonコピーする編集するdef toggle_flash(self):
    color = "yellow" if self.is_flashing else "black"
    self.label.setStyleSheet(f"color: {color}; background: transparent;")
    self.is_flashing = not self.is_flashing

✅ ポイント

  • QTimer機能ごとに分離することで処理がシンプル&保守性UP
  • ゲームのような状態変化の演出にも応用しやすい書き方

🔹 ③ 背景画像+透明ボタンで“演出重視UI”を実現

📌 目的は?

  • UIの印象を“アプリっぽい”から“作品っぽい”ものへ寄せる

🔧 実装方法

pythonコピーする編集するbtn.setStyleSheet(
    "background: transparent; border: 2px solid red; color: white; font-size: 20px;"
)
  • ボタン背景を透明にし、枠線だけ赤く強調
  • 背景画像と調和して、ボタンが“浮いているように”見える

✅ ポイント

  • PyQtのsetStyleSheetでCSS風のスタイルを付けられる
  • 色・サイズ・フォントをうまく使えば、GUIで世界観を演出できる

🔹 ④ QLineEditのテキスト中央揃えの正しい方法

📌 初心者がやりがちなミス

pythonコピーする編集するself.input_field.setStyleSheet("text-align: center;")

→ 実はこれは 無効

🔧 正しい書き方

pythonコピーする編集するself.input_field.setAlignment(Qt.AlignCenter)

✅ ポイント

  • text-align は Web の CSS 用語で、PyQt では無効
  • PyQt のテキスト中央揃えは .setAlignment() が正解

🔹 ⑤ フォーマット整形の工夫(ミリ秒 → 分:秒:小数)

pythonコピーする編集するdef format_time(self, milliseconds):
    total_seconds = milliseconds // 1000
    minutes = total_seconds // 60
    seconds = total_seconds % 60
    hundredths = (milliseconds % 1000) // 10
    return f"{minutes:02}:{seconds:02}:{hundredths:02}"

✅ ポイント

  • :02の書き方で常に2桁表示(例:5 → 05)
  • ミリ秒単位でも視認性とデザインを両立したタイマー表記にしている
  • 見た目が「精密・近未来風」になり、演出力UP

🟣 まとめと今後やってみたいこと

今回、普段使っていたポモドーロタイマーを「自分の好きなデザインで作ってみたい」という理由から、
PyQtを使ってオリジナルのカウントダウンタイマーを制作しました。


🔧 作ってよかった点

  • とにかく楽しかった
     → 自分好みのUIを自由に組めるのはテンション上がります
  • GUI開発の流れがわかるようになった
     → 画面遷移、ウィジェットの配置、イベント処理…PyQtの基本が実体験で学べました
  • Pythonだけで“ちゃんと動くアプリ”が作れたという小さな達成感

🚀 今後の改良アイデア

  • タイマーが0になったときに音が鳴る通知機能を追加したい
  • 時間プリセット(25分/45分など)をいくつか用意して、ボタン1つで切り替え可能にしたい
  • ゆくゆくは**スマホアプリ化(Kivyなど)**にも挑戦してみたい

💬 PyQtやUI制作の魅力について

「見た目にこだわれるプログラミング」って、やっぱり楽しいんだなと改めて感じました。

PyQtはとっつきやすく、GUI開発の取っかかりとしてとても優秀です。
ちょっとずつ手を加えるだけで、アプリが“作品”になっていく過程が面白いです。


✅ 最後に:この記事から得られる読者のメリット

このブログを通して、少しでも以下のようなことを感じてもらえたら嬉しいです。

  • GUIって面白そう!」「PyQtやってみたい!」と思ってもらえる
  • 自分でもUIアプリが作れるんだという可能性を感じてもらえる
  • 「お、これ真似して作ってみたいな」と思った方は、コードもあるのでぜひ挑戦してみてください!

このアプリはあくまで“個人の遊び”から始まったものですが、
誰かにとってPyQtやGUI制作のきっかけになれたら、それが一番うれしいです😊

コメント

タイトルとURLをコピーしました