あたも技術ブログ

セットジャパンコーポレーションの社員が運営しています。

【Swift】xmlで管理している色情報を簡単に扱う

はじめに

今回は、xmlで色を管理した場合に、簡単に扱えるようにしたいと思います。

xmlについて

以下のフォーマットでxmlを用意します。

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <color name="buttonBackGround">#285176</color>
    <color name="buttonText">#305c83</color>
    .
    .
    .
</resources>

コード

今回は、色に関してのみですが、xmlファイル1つで色々とまとめて管理することができるので、使い回しがしやすいように、クラスを分けています。

class XMLUtil: NSObject,  XMLParserDelegate {
    
    var parser: XMLParser!
    
    var compleationBlock: (([String : String]?)->Void) = {_ in}
    var element: String!
    var attribute: String!
    var tmpDic: [String : String]!
    var tmpKey: String!
    
    public func parse(fileName: String, element: String! = nil, attribute: String! = nil, compleation: @escaping (([String : String]?)->Void)) {
        self.element = element
        self.attribute = attribute
        compleationBlock = compleation
        
        guard let tmpData: Data = loadAssetFile(name: fileName) else {
            self.compleationBlock(nil)
            return
        }
        
        parser = XMLParser(data: tmpData)
        parser.delegate = self
        
        let _ = parser.parse()
        
    }
    
    private func loadAssetFile(name: String) -> Data! {
        var data: Data! = nil
        // Assetsから取得
        if #available(iOS 9.0, *) {
            if let tmpData = NSDataAsset(name: name) {
                data = tmpData.data
            }
        } 
        return data
    }
    
    // MARK: - NSXMLParserDelegate
    
   public func parserDidStartDocument(_ parser: XMLParser) {
        tmpDic = [ : ]
    }
    
    public func parser(_ parser: XMLParser, didStartElement elementName: String, namespaceURI: String?, qualifiedName qName: String?, attributes attributeDict: [String : String] = [:]) {
        
        guard let _ = element else {
            tmpKey = elementName
            return
        }
        guard let _ = attribute else {
            tmpKey = element
            return
        }
        if elementName == element {
            guard let key = attributeDict[attribute] else {
                return
            }
            tmpKey = key
        }
    }
    
    public func parser(_ parser: XMLParser, didEndElement elementName: String, namespaceURI: String?, qualifiedName qName: String?) {
        tmpKey = nil
    }
    
    public func parser(_ parser: XMLParser, foundCharacters string: String) {
        guard let _ = tmpKey else {
            return
        }
        tmpDic[tmpKey] = string
    }
    
    public func parserDidEndDocument(_ parser: XMLParser) {
        self.compleationBlock(tmpDic)
    }
}
class XMLColorUtil: NSObject {
    private var colors: [String : String]! = nil
    
    static var shared: XMLColorUtil = {
        let instance = XMLColorUtil()
        let res = instance.loadColors()

        return instance
    }()
    private override init() {
    }
   
    internal func loadColors() {
        var finish = false
        XMLUtil().parse(fileName: "colors", element: "color", attribute: "name") {
            dic in
            if dic != nil {
                self.colors = dic
            }
            finish = true
        }
        
    }
    
    public func getColor(key: String = #function) -> UIColor {
        guard let _ = colors else {
            return .white
        }
        guard let hexColor = colors[key] else {
            return .white
        }
        return UIColor(hex: hexColor)
    }
}
extension UIColor {
    convenience init(hex: String) {
        let range = NSMakeRange(0, rgbString.characters.count)
        let hexStr = (rgbString as NSString).replacingOccurrences(of: "[^0-9a-fA-F]", with: "", options: .regularExpression, range: range)
        
        var r: Float = 1
        var g: Float = 1
        var b: Float = 1
        let a: Float = 1
        if hexStr.characters.count == 6 {
            if let num = Int(hexStr, radix: 16) {
                r = Float((num >> 16) & 0xFF) / 255.0
                g = Float((num >> 8) & 0xFF) / 255.0
                b = Float((num) & 0xFF) / 255.0
            }
        }
        self.init(red: CGFloat(r), green: CGFloat(g), blue: CGFloat(b), alpha: CGFloat(a))
    }
}

色クラス

class AppColor: NSObject {
    private let util = XMLColorUtil.shared
    
    // xml上のnameと合わせる
    public var buttonBackGround: UIColor {
        return util.getColor()
    }
    public var buttonText: UIColor {
        return util.getColor()
    }
    .
    .
    .
}

このクラスに上にxmlでname属性に設定した値をプロパティとして定義し、XMLColorUtilクラスのgetColor()を呼びます

getColor()の引数の初期値は#functionなので、呼び出し元のメソッド名を取得するようになっています。

XMLColorUtil().getColor(key: "buttonBackGround")とすることもできます。

使い方

let appColor = AppColor()

let button = UIButton(frame: CGRect(origin: .zero, size: CGSize(width: 100, height: 60)))

button.backgroundColor = appColor.buttonBackGround
button.setTitleColor(appColor.buttonText, for: .normal)

使用する色が増えた際には、xml編集後、AppColorクラスにプロパティを追加するだけで使えるようになります。

【C#】Windowsフォーム アプリケーションで拡大・縮小ラベルユーザコントロール

 今回はWindowsフォーム アプリケーションで前回に作成した角丸コーナーラベルを使用して拡大、縮小するラベルっぽいものを作りたいと思います。
 プロパティで指定した基準点(左上、左下、中央、右上、右下)から拡大、縮小します。
 TableLayoutPanelを利用してそれっぽくというか力技で拡大、縮小しています。速度を求める場合はしっかり計算してレンダリングし直した方が良いでしょう。

using System;
using System.Drawing;
using System.Windows.Forms;

namespace ZoomingSample
{
  /// <summary>
  /// ダブルバッファクラス
  /// </summary>
  public class DoubleBuffer : IDisposable
  {
    #region メンバ変数
    /// <summary>
    /// ダブルバッファリングオブジェクト
    /// </summary>
    private BufferedGraphics _Buffer = null;

    /// <summary>
    /// バッファリングを行うコントロール
    /// </summary>
    private Control _Control = null;
    #endregion

    #region プロパティ
    /// <summary>
    /// グラフィックオブジェクト
    /// </summary>
    public Graphics Graphics
    {
      get
      {
        return this._Buffer.Graphics;
      }
    }
    #endregion

    #region コンストラクタ
    /// <summary>
    /// クラスの新しいインスタンスを初期化します。
    /// </summary>
    /// <param name="control">バッファリングを行うコントロール</param>
    public DoubleBuffer(Control control)
    {
      this.Dispose();

      var currentContext = BufferedGraphicsManager.Current;
      this._Buffer = currentContext.Allocate(control.CreateGraphics(), control.DisplayRectangle);

      if (this._Control != null)
      {
        this._Control.Paint += new System.Windows.Forms.PaintEventHandler(this.Paint);
      }
    }
    #endregion

    #region デストラクタ
    /// <summary>
    /// デストラクタ
    /// </summary>
    ~DoubleBuffer()
    {
      Dispose();
    }
    #endregion

    #region publicメソッド
    /// <summary>
    /// 解放処理を行います。
    /// </summary>
    public void Dispose()
    {
      if (this._Buffer != null)
      {
        this._Buffer = null;
      }
      if (this._Control != null)
      {
        this._Control.Paint -= new System.Windows.Forms.PaintEventHandler(this.Paint);
      }
    }

    /// <summary>
    /// 更新を行います。
    /// </summary>
    public void Refresh()
    {
      if (this._Buffer != null)
      {
        this._Buffer.Render();
      }
    }
    #endregion

    #region privateメソッド
    /// <summary>
    /// 描画処理を行います。
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    private void Paint(object sender, PaintEventArgs e)
    {
      Refresh();
    }
    #endregion
  }
}

 角丸コーナーラベルの実装コードです。

namespace ZoomingSample
{
  partial class RoundCornerLabel
  {
    /// <summary> 
    /// 必要なデザイナー変数です。
    /// </summary>
    private System.ComponentModel.IContainer components = null;

    /// <summary> 
    /// 使用中のリソースをすべてクリーンアップします。
    /// </summary>
    /// <param name="disposing">マネージ リソースが破棄される場合 true、破棄されない場合は false です。</param>
    protected override void Dispose(bool disposing)
    {
      if (disposing && (components != null))
      {
        components.Dispose();
      }
      base.Dispose(disposing);
    }

    #region コンポーネント デザイナーで生成されたコード

    /// <summary> 
    /// デザイナー サポートに必要なメソッドです。このメソッドの内容を 
    /// コード エディターで変更しないでください。
    /// </summary>
    private void InitializeComponent()
    {
      this.pic = new System.Windows.Forms.PictureBox();
      ((System.ComponentModel.ISupportInitialize)(this.pic)).BeginInit();
      this.SuspendLayout();
      // 
      // pic
      // 
      this.pic.BackColor = System.Drawing.Color.Transparent;
      this.pic.Dock = System.Windows.Forms.DockStyle.Fill;
      this.pic.Location = new System.Drawing.Point(0, 0);
      this.pic.Margin = new System.Windows.Forms.Padding(0);
      this.pic.Name = "pic";
      this.pic.Size = new System.Drawing.Size(200, 150);
      this.pic.TabIndex = 0;
      this.pic.TabStop = false;
      // 
      // RoundCornerLabel
      // 
      this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 12F);
      this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
      this.Controls.Add(this.pic);
      this.Margin = new System.Windows.Forms.Padding(0);
      this.Name = "RoundCornerLabel";
      this.Size = new System.Drawing.Size(200, 150);
      ((System.ComponentModel.ISupportInitialize)(this.pic)).EndInit();
      this.ResumeLayout(false);

    }

    #endregion

    private System.Windows.Forms.PictureBox pic;
  }
}
using System;
using System.ComponentModel;
using System.Drawing;
using System.Windows.Forms;
using System.Drawing.Drawing2D;

namespace ZoomingSample
{
  /// <summary>
  /// 角丸ラベルユーザコントロール
  /// </summary>
  public partial class RoundCornerLabel : UserControl
  {
    #region メンバ変数
    /// <summary>
    /// ダブルバッファクラス
    /// </summary>
    private DoubleBuffer _DoubleBuffer = null;

    /// <summary>
    /// コントロールに関連付けられたテキスト
    /// </summary>
    private string _DisplayText = "";

    /// <summary>
    /// 文字列の配置
    /// </summary>
    private ContentAlignment _TextAlign = ContentAlignment.MiddleCenter;

    /// <summary>
    /// コーナーの角丸のサイズ(直径)
    /// </summary>
    private int _CornerR = 10;

    /// <summary>
    /// コーナーの線の太さ
    /// </summary>
    private int _PenWidth = 5;

    /// <summary>
    /// コーナーの線の色
    /// </summary>
    private Color _CornerColor = Color.Blue;

    /// <summary>
    /// 矩形内の背景色
    /// </summary>
    private Color _InnerColor = SystemColors.Window;
    #endregion

    #region プロパティ
    /// <summary>
    /// コントロールに関連付けられたテキスト
    /// </summary>
    [Browsable(true)]
    [Category("表示")]
    [Description("コントロールに関連付けられたテキストです。")]
    public string DisplayText
    {
      get
      {
        return this._DisplayText;
      }
      set
      {
        this._DisplayText = value;
        Refresh();
      }
    }

    /// <summary>
    /// コントロールに表示される文字列の配置
    /// </summary>
    [Browsable(true)]
    [Category("表示")]
    [Description("コントロールに表示される文字列の配置です。")]
    public ContentAlignment TextAlign
    {
      get
      {
        return this._TextAlign;
      }
      set
      {
        this._TextAlign = value;
        Refresh();
      }
    }

    /// <summary>
    /// 角の丸さ
    /// </summary>
    [Browsable(true)]
    [Category("表示")]
    [Description("角の丸さを指定します。(半径)")]
    public int CornerR
    {
      get
      {
        return (int)(this._CornerR / 2);
      }
      set
      {
        if (0 < value)
        {
          this._CornerR = value * 2;
        }
        else
        {
          // 0以下が設定された場合、強制的に1にする
          this._CornerR = 1;
        }

        //RenewPadding();
        Refresh();
      }
    }

    /// <summary>
    /// 枠線の色
    /// </summary>
    [Browsable(true)]
    [Category("表示")]
    [Description("外枠の色を指定します。")]
    public Color CornerColor
    {
      get
      {
        return this._CornerColor;
      }
      set
      {
        this._CornerColor = value;
        Refresh();
      }
    }

    /// <summary>
    /// 枠線の幅を指定します。
    /// </summary>
    [Browsable(true)]
    [Category("表示")]
    [Description("枠線の太さを指定します。")]
    public int PenWidth
    {
      get
      {
        return this._PenWidth;
      }
      set
      {
        if (0 <= value)
        {
          this._PenWidth = value;
        }
        else
        {
          // 0以下が指定された場合、強制的に0にする
          this._PenWidth = 0;
        }

        //RenewPadding();
        Refresh();
      }
    }

    /// <summary>
    /// コントロールの矩形内の背景色
    /// </summary>
    [Browsable(true)]
    [Category("表示")]
    [Description("コントロールの矩形内の背景色を指定します。")]
    public Color InnerColor
    {
      get
      {
        return this._InnerColor;
      }
      set
      {
        this._InnerColor = value;
        Refresh();
      }
    }
    #endregion

    #region コンストラクタ
    /// <summary>
    /// クラスの新しいインスタンスを初期化します。
    /// </summary>
    public RoundCornerLabel()
    {
      InitializeComponent();

      // コントロールのサイズが変更された時にPaintイベントを発生させる
      SetStyle(ControlStyles.ResizeRedraw, true);
      SetStyle(ControlStyles.SupportsTransparentBackColor, true);
      // ピクチャボックスでダブルバッファを行うようにインスタンス生成
      this._DoubleBuffer = new DoubleBuffer(this.pic);

      this.BackColor = Color.Transparent;
    }
    #endregion

    #region override
    /// <summary>
    /// PaintBackground イベントを発生させます。
    /// </summary>
    /// <param name="e">イベント引数</param>
    protected override void OnPaintBackground(PaintEventArgs e)
    {
      // バックグランドの描画を行うとWhiteの挿入がされるため
      // 何もしないようにする
    }

    /// <summary>
    /// Load イベントを発生させます。
    /// </summary>
    /// <param name="e">イベント引数</param>
    protected override void OnLoad(EventArgs e)
    {
      base.OnLoad(e);
    }

    /// <summary>
    /// Paint イベントを発生させます。
    /// </summary>
    /// <param name="e">イベント引数</param>
    protected override void OnPaint(PaintEventArgs e)
    {
      Draw(e.Graphics);
    }
    #endregion

    #region private
    /// <summary>
    /// 描画を行います。
    /// </summary>
    /// <param name="g">グラフィックオブジェクト</param>
    private void Draw(Graphics g)
    {
      // 描画品質設定
      g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
      g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAlias;
      g.PixelOffsetMode = System.Drawing.Drawing2D.PixelOffsetMode.HighQuality;

      // 背景用のImageオブジェクトを作成する
      var backrect = new Rectangle(0, 0, this.Width, this.Height);
      var backColor = this.Parent != null ? this.Parent.BackColor : SystemColors.Control;
      using (var brush = new SolidBrush(backColor))
      {
        g.FillRectangle(brush, backrect);
      }

      using (var cornerPen = new Pen(this._CornerColor, this._PenWidth))
      {
        // 変数初期化
        var harfPenWidth = GetHarfPenWidth();

        // ペンとブラシ初期化
        var offset = this._PenWidth;
        var rect = new Rectangle(offset, offset, this.Width, this.Height);
        using (var brush = new SolidBrush(this._InnerColor))
        using (var graphPath = new GraphicsPath())
        {
          graphPath.AddArc(harfPenWidth, harfPenWidth, this._CornerR, this._CornerR, 180, 90);
          graphPath.AddArc(this.Width - this._CornerR - harfPenWidth, harfPenWidth, this._CornerR, this._CornerR, 270, 90);
          graphPath.AddArc(this.Width - this._CornerR - harfPenWidth, this.Height - this._CornerR - harfPenWidth, this._CornerR, this._CornerR, 0, 90);
          graphPath.AddArc(harfPenWidth, this.Height - this._CornerR - harfPenWidth, this._CornerR, this._CornerR, 90, 90);
          graphPath.CloseFigure();

          // パス塗り
          g.FillPath(brush, graphPath);

          // ペンの太さが1以上なら外枠描画
          if (0 < this._PenWidth)
          {
            g.DrawPath(cornerPen, graphPath);
          }
        }

        // テキストが設定されている場合は文字を書く
        var size = new Size(this.Width - offset * 2, this.Height - offset * 2);
        var fontSize = CalcFontSize(this._DisplayText, size, g);
        if (fontSize <= 0) { fontSize = 0.1F; }

        var font = new Font(this.Font.Name, fontSize);
        var stringFormat = new StringFormat();
        var num = (Int32)System.Math.Log((Double)this._TextAlign, 2);
        stringFormat.LineAlignment = (StringAlignment)(num / 4);
        stringFormat.Alignment = (StringAlignment)(num % 4);

        // 文字描画用のブラシを生成する
        using (var foreColorBrush = new SolidBrush(this.ForeColor))
        {
          // 微妙に右にずれるため-3して補正する
          rect.X = rect.X - 3;
          g.DrawString(this._DisplayText, font, foreColorBrush, rect, stringFormat);
        }
      }
    }

    /// <summary>
    /// Paddingサイズを更新します。
    /// </summary>
    private void RenewPadding()
    {
      var harfCornerR = (int)(this._CornerR / 2);
      var adjust = (int)(System.Math.Cos(45 * System.Math.PI / 180) * harfCornerR);
      this.Padding = new Padding(harfCornerR + this._PenWidth + 2 - adjust);
    }

    /// <summary>
    /// コーナー線の幅の1/2の値を取得します。
    /// </summary>
    /// <returns>コーナー線の幅の1/2の値</returns>
    private int GetHarfPenWidth()
    {
      return (int)System.Math.Floor((double)(this._PenWidth) / 2 + 0.5);
    }

    /// <summary>
    /// 指定されたグラフィックオブジェクトに描画する最適なフォントサイズを算出します。
    /// </summary>
    /// <param name="str">出力する文字列</param>
    /// <param name="size">サイズ</param>
    /// <param name="g">描画するグラフィックオブジェクト</param>
    /// <returns>フォントサイズ</returns>
    private float CalcFontSize(string str, Size size, Graphics g)
    {
      var s = new SizeF(0.1F, 0.1F);
      var a = 0.1F;
      var b = 0.1F;

      if (!string.IsNullOrEmpty(str))
      {
        s = g.MeasureString(str, new Font(this.Font.Name, 1));
        a = (size.Width / s.Width);
        b = (size.Height / s.Height);
      }
      return (a < b) ? a : a;
    }
    #endregion
  }
}

 拡大、縮小ラベルの実装コードです。拡大、縮小時に割込みをブロックしていません。実際に利用する場合は拡大中、縮小中か判定して処理の割込みはしないよう考慮した方が良いでしょう。

namespace ZoomingSample
{
  partial class ZoomingLabel
  {
    /// <summary> 
    /// 必要なデザイナー変数です。
    /// </summary>
    private System.ComponentModel.IContainer components = null;

    /// <summary> 
    /// 使用中のリソースをすべてクリーンアップします。
    /// </summary>
    /// <param name="disposing">マネージ リソースが破棄される場合 true、破棄されない場合は false です。</param>
    protected override void Dispose(bool disposing)
    {
      if (disposing && (components != null))
      {
        components.Dispose();
      }
      base.Dispose(disposing);
    }

    #region コンポーネント デザイナーで生成されたコード

    /// <summary> 
    /// デザイナー サポートに必要なメソッドです。このメソッドの内容を 
    /// コード エディターで変更しないでください。
    /// </summary>
    private void InitializeComponent()
    {
      this.tableLayoutPanel = new System.Windows.Forms.TableLayoutPanel();
      this.SuspendLayout();
      // 
      // tableLayoutPanel
      // 
      this.tableLayoutPanel.BackColor = System.Drawing.Color.Transparent;
      this.tableLayoutPanel.ColumnCount = 3;
      this.tableLayoutPanel.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 25F));
      this.tableLayoutPanel.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 50F));
      this.tableLayoutPanel.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 25F));
      this.tableLayoutPanel.Dock = System.Windows.Forms.DockStyle.Fill;
      this.tableLayoutPanel.Font = new System.Drawing.Font("メイリオ", 14.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(128)));
      this.tableLayoutPanel.Location = new System.Drawing.Point(0, 0);
      this.tableLayoutPanel.Margin = new System.Windows.Forms.Padding(0);
      this.tableLayoutPanel.Name = "tableLayoutPanel";
      this.tableLayoutPanel.RowCount = 3;
      this.tableLayoutPanel.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 25F));
      this.tableLayoutPanel.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 50F));
      this.tableLayoutPanel.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 25F));
      this.tableLayoutPanel.Size = new System.Drawing.Size(400, 300);
      this.tableLayoutPanel.TabIndex = 0;
      // 
      // ZoomingLabel
      // 
      this.AutoScaleDimensions = new System.Drawing.SizeF(12F, 28F);
      this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
      this.BackColor = System.Drawing.Color.Transparent;
      this.Controls.Add(this.tableLayoutPanel);
      this.Font = new System.Drawing.Font("メイリオ", 14.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(128)));
      this.Margin = new System.Windows.Forms.Padding(0);
      this.Name = "ZoomingLabel";
      this.Size = new System.Drawing.Size(400, 300);
      this.ResumeLayout(false);

    }

    #endregion

    private System.Windows.Forms.TableLayoutPanel tableLayoutPanel;
  }
}
using System;
using System.ComponentModel;
using System.Drawing;
using System.Windows.Forms;

namespace ZoomingSample
{
  /// <summary>
  /// 拡大縮小ラベルユーザコントロール
  /// </summary>
  public partial class ZoomingLabel : UserControl
  {
    #region Enum定義
    /// <summary>
    /// 拡大するときの方向を示す定義
    /// </summary>
    public enum ZoomDirection
    {
      /// <summary>
      /// 右下方向(左上を基準点にして拡大します)
      /// </summary>
      RightDown = 0,
      /// <summary>
      /// 右上方向(左下を基準点にして拡大します)
      /// </summary>
      RightUp = 1,
      /// <summary>
      /// 左下方向(右上を基準点にして拡大します)
      /// </summary>
      LeftDown = 2,
      /// <summary>
      /// 左上方向(右下を基準点にして拡大します)
      /// </summary>
      LeftUp = 3,
      /// <summary>
      /// 全方向(中心点を基準点にして拡大します)
      /// </summary>
      AllSide = 4
    }
    #endregion

    #region メンバ変数
    /// <summary>
    /// コントロールに関連付けられたテキスト
    /// </summary>
    private string _DisplayText = "";

    /// <summary>
    /// 文字列の配置
    /// </summary>
    private ContentAlignment _TextAlign = ContentAlignment.MiddleCenter;

    /// <summary>
    /// コーナーの角丸のサイズ(直径)
    /// </summary>
    private int _CornerR = 10;

    /// <summary>
    /// コーナーの線の太さ
    /// </summary>
    private int _PenWidth = 5;

    /// <summary>
    /// コーナーの線の色
    /// </summary>
    private Color _CornerColor = Color.Blue;

    /// <summary>
    /// 矩形内の背景色
    /// </summary>
    private Color _InnerColor = SystemColors.Window;

    /// <summary>
    /// 拡大を行う方向
    /// </summary>
    /// <remarks>デフォルトは中心点から全方向拡大</remarks>
    private ZoomDirection _Direction = ZoomDirection.LeftDown;

    /// <summary>
    /// テーブルレイアウトパネルの縮小・拡大時の刻み幅
    /// </summary>
    private float _PitchWidth = 0.0F;

    /// <summary>
    /// テーブルレイアウトパネルの縮小・拡大時の刻み高さ
    /// </summary>
    private float _PitchHeight = 0.0F;

    /// <summary>
    /// テーブルレイアウトパネルの元の幅
    /// </summary>
    private float _OriginalWidth = 0.0F;

    /// <summary>
    /// テーブルレイアウトパネルの元の高さ
    /// </summary>
    private float _OriginalHeight = 0.0F;

    /// <summary>
    /// 拡大・縮小時のアニメーションの更新間隔
    /// </summary>
    private int _AnimationInterval = 1;

    /// <summary>
    /// 拡大・縮小で利用するタイマー
    /// </summary>
    private Timer _ZoomTimer;
    #endregion

    #region プロパティ
    /// <summary>
    /// コントロールに関連付けられたテキスト
    /// </summary>
    [Browsable(true)]
    [Category("表示")]
    [Description("コントロールに関連付けられたテキストです。")]
    public string DisplayText
    {
      get
      {
        return this._DisplayText;
      }
      set
      {
        this._DisplayText = value;
        SetRoundCornerLabel();
        this.Refresh();
      }
    }

    /// <summary>
    /// コントロールに表示される文字列の配置
    /// </summary>
    [Browsable(true)]
    [Category("表示")]
    [Description("コントロールに表示される文字列の配置です。")]
    public ContentAlignment TextAlign
    {
      get
      {
        return this._TextAlign;
      }
      set
      {
        this._TextAlign = value;
        SetRoundCornerLabel();
        this.Refresh();
      }
    }

    /// <summary>
    /// 矩形内の背景色
    /// </summary>
    [Browsable(true)]
    [Category("表示")]
    [Description("コントロールの矩形内の背景色を取得または設定します。")]
    public Color InnerColor
    {
      get
      {
        return this._InnerColor;
      }
      set
      {
        this._InnerColor = value;
        SetRoundCornerLabel();
        this.Refresh();
      }
    }

    /// <summary>
    /// 角の丸さ
    /// </summary>
    [Browsable(true)]
    [Category("表示")]
    [Description("角の丸さを指定します。(半径)")]
    public int CornerR
    {
      get
      {
        return this._CornerR;
      }
      set
      {
        this._CornerR = value;
        SetRoundCornerLabel();
        this.Refresh();
      }
    }

    /// <summary>
    /// 枠線の色
    /// </summary>
    [Browsable(true)]
    [Category("表示")]
    [Description("外枠の色を指定します。")]
    public Color ConerColor
    {
      get
      {
        return this._CornerColor;
      }
      set
      {
        this._CornerColor = value;
        SetRoundCornerLabel();
        this.Refresh();
      }
    }

    /// <summary>
    /// 枠線の幅を指定します。
    /// </summary>
    [Browsable(true)]
    [Category("表示")]
    [Description("枠線の太さを指定します。")]
    public int PenWidth
    {
      get
      {
        return this._PenWidth;
      }
      set
      {
        this._PenWidth = value;
        SetRoundCornerLabel();
        this.Refresh();
      }
    }

    /// <summary>
    /// 拡大時の方向を指定します。
    /// </summary>
    [Browsable(true)]
    [Category("表示")]
    [Description("拡大時の方向を指定します。")]
    public ZoomDirection Direction
    {
      get
      {
        return this._Direction;
      }
      set
      {
        this._Direction = value;
        CreateTebleLayout();
        this.Refresh();
      }
    }

    /// <summary>
    /// 拡大・縮小時のアニメーションの更新間隔を指定します。
    /// </summary>
    [Browsable(true)]
    [Category("動作")]
    [Description("拡大・縮小時のアニメーションの更新間隔を指定します。")]
    public int AnimationInterval
    {
      get
      {
        return this._AnimationInterval;
      }
      set
      {
        this._AnimationInterval = value;
      }
    }
    #endregion

    #region コンストラクタ
    /// <summary>
    /// コンストラクタ
    /// </summary>
    public ZoomingLabel()
    {
      InitializeComponent();
      this.BackColor = Color.Transparent;
      this.tableLayoutPanel.BackColor = SystemColors.Control;
      this.DoubleBuffered = true;

      // 元の角丸ラベルユーザコントロールのサイズを保持しておく
      this._OriginalWidth = this.Width / 2;
      this._OriginalHeight = this.Height / 2;
      CreateTebleLayout();
      CalcPitch();
    }
    #endregion

    #region override
    /// <summary>
    /// Load イベントを発生させます。
    /// </summary>
    /// <param name="e"></param>
    protected override void OnLoad(EventArgs e)
    {
      base.OnLoad(e);
    }

    /// <summary>
    /// FontChanged イベントを発生させます。
    /// </summary>
    /// <param name="e"></param>
    protected override void OnFontChanged(EventArgs e)
    {
      base.OnFontChanged(e);
      SetRoundCornerLabel();
      this.Refresh();
    }

    /// <summary>
    /// BackColorChanged イベントを発生させます。
    /// </summary>
    /// <param name="e"></param>
    protected override void OnBackColorChanged(EventArgs e)
    {
      base.OnBackColorChanged(e);
      this.tableLayoutPanel.BackColor = this.BackColor;
      this.Refresh();
    }

    /// <summary>
    /// Resize イベントを発生させます。
    /// </summary>
    /// <param name="e"></param>
    protected override void OnResize(EventArgs e)
    {
      base.OnResize(e);
      // 元の角丸ラベルユーザコントロールのサイズを保持しておく
      this._OriginalWidth = this.Width / 2;
      this._OriginalHeight = this.Height / 2;
      CreateTebleLayout();
      CalcPitch();
    }
    #endregion

    #region public
    /// <summary>
    /// 指定された方向へ拡大処理を行います。
    /// </summary>
    /// <remarks>拡大済の場合、それ以上大きくはならない</remarks>
    public void ZoomIn()
    {
      this._ZoomTimer = new Timer();
      this._ZoomTimer.Interval = _AnimationInterval;
      this._ZoomTimer.Tick += new EventHandler(this.ZoomInTimer_Tick);
      this._ZoomTimer.Enabled = true;
    }

    /// <summary>
    /// 拡大時の処理を行います。
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    private void ZoomInTimer_Tick(object sender, EventArgs e)
    {
      var control = this.tableLayoutPanel.Controls[0] as RoundCornerLabel;
      var h1 = 0.0F;
      var w1 = 0.0F;
      var h2 = 0.0F;
      var w2 = 0.0F;
      var col1 = 0;
      var row1 = 0;
      var col2 = 0;
      var row2 = 0;

      if (this._Direction == ZoomDirection.RightDown)
      {
        // 左上基準、右下方向拡大の場合
        h1 = this.tableLayoutPanel.RowStyles[1].Height - this._PitchHeight;
        w1 = this.tableLayoutPanel.ColumnStyles[1].Width - this._PitchWidth;
        h2 = this.tableLayoutPanel.RowStyles[0].Height + this._PitchHeight;
        w2 = this.tableLayoutPanel.ColumnStyles[0].Width + this._PitchWidth;
        row1 = 1;
        col1 = 1;
      }
      else if (this._Direction == ZoomDirection.RightUp)
      {
        // 左下基準、右上方向拡大の場合
        h1 = this.tableLayoutPanel.RowStyles[0].Height - this._PitchHeight;
        w1 = this.tableLayoutPanel.ColumnStyles[1].Width - this._PitchWidth;
        h2 = this.tableLayoutPanel.RowStyles[1].Height + this._PitchHeight;
        w2 = this.tableLayoutPanel.ColumnStyles[0].Width + this._PitchWidth;
        row2 = 1;
        col1 = 1;
      }
      else if (this._Direction == ZoomDirection.LeftDown)
      {
        // 右上基準、左下方向拡大の場合
        h1 = this.tableLayoutPanel.RowStyles[1].Height - this._PitchHeight;
        w1 = this.tableLayoutPanel.ColumnStyles[0].Width - this._PitchWidth;
        h2 = this.tableLayoutPanel.RowStyles[0].Height + this._PitchHeight;
        w2 = this.tableLayoutPanel.ColumnStyles[1].Width + this._PitchWidth;
        row1 = 1;
        col2 = 1;
      }
      else if (this._Direction == ZoomDirection.LeftUp)
      {
        // 右下基準、左上方向拡大の場合
        h1 = this.tableLayoutPanel.RowStyles[0].Height - this._PitchHeight;
        w1 = this.tableLayoutPanel.ColumnStyles[0].Width - this._PitchWidth;
        h2 = this.tableLayoutPanel.RowStyles[1].Height + this._PitchHeight;
        w2 = this.tableLayoutPanel.ColumnStyles[1].Width + this._PitchWidth;
        row2 = 1;
        col2 = 1;
      }
      else
      {
        h1 = this.tableLayoutPanel.RowStyles[0].Height - 0.5F;
        w1 = this.tableLayoutPanel.ColumnStyles[0].Width - 0.5F;
        h2 = this.tableLayoutPanel.RowStyles[1].Height + 0.5F;
        w2 = this.tableLayoutPanel.ColumnStyles[1].Width + 0.5F;
      }


      // コントロールのサイズ以上となった場合、Timerを停止し拡大を終える
      if ((this.Width <= w2 || this.Height <= h2) || (w1 <= 0 || h1 <= 0))
      {
        this._ZoomTimer.Enabled = false;
        this._ZoomTimer.Tick -= this.ZoomOutTimer_Tick;
        return;
      }

      if (this._Direction != ZoomDirection.AllSide)
      {
        this.tableLayoutPanel.RowStyles[row1].Height = h1;
        this.tableLayoutPanel.ColumnStyles[col1].Width = w1;
        this.tableLayoutPanel.RowStyles[row2].Height = h2;
        this.tableLayoutPanel.ColumnStyles[col2].Width = w2;
      }
      else
      {
        this.tableLayoutPanel.RowStyles[0].Height = h1;
        this.tableLayoutPanel.RowStyles[1].Height = h2;
        this.tableLayoutPanel.RowStyles[2].Height = h1;
        this.tableLayoutPanel.ColumnStyles[0].Width = w1;
        this.tableLayoutPanel.ColumnStyles[1].Width = w2;
        this.tableLayoutPanel.ColumnStyles[2].Width = w1;
      }
    }

    /// <summary>
    /// 指定された逆方向へ縮小処理を行います。
    /// </summary>
    /// <remarks>拡大時のみ縮小される。元のコントロールのサイズより小さくはならない</remarks>
    public void ZoomOut()
    {
      this._ZoomTimer = new Timer();
      this._ZoomTimer.Interval = _AnimationInterval;
      this._ZoomTimer.Tick += new EventHandler(this.ZoomOutTimer_Tick);
      this._ZoomTimer.Enabled = true;
    }

    /// <summary>
    /// 縮小時の処理を行います。
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    private void ZoomOutTimer_Tick(object sender, EventArgs e)
    {
      var control = this.tableLayoutPanel.Controls[0] as RoundCornerLabel;
      var h1 = 0.0F;
      var w1 = 0.0F;
      var h2 = 0.0F;
      var w2 = 0.0F;
      var col1 = 0;
      var row1 = 0;
      var col2 = 0;
      var row2 = 0;

      if (this._Direction == ZoomDirection.RightDown)
      {
        // 左上基準、右下方向拡大の場合
        h1 = this.tableLayoutPanel.RowStyles[1].Height + this._PitchHeight;
        w1 = this.tableLayoutPanel.ColumnStyles[1].Width + this._PitchWidth;
        h2 = this.tableLayoutPanel.RowStyles[0].Height - this._PitchHeight;
        w2 = this.tableLayoutPanel.ColumnStyles[0].Width - this._PitchWidth;
        row1 = 1;
        col1 = 1;
      }
      else if (this._Direction == ZoomDirection.RightUp)
      {
        // 左下基準、右上方向拡大の場合
        h1 = this.tableLayoutPanel.RowStyles[0].Height + this._PitchHeight;
        w1 = this.tableLayoutPanel.ColumnStyles[1].Width + this._PitchWidth;
        h2 = this.tableLayoutPanel.RowStyles[1].Height - this._PitchHeight;
        w2 = this.tableLayoutPanel.ColumnStyles[0].Width - this._PitchWidth;
        row2 = 1;
        col1 = 1;
      }
      else if (this._Direction == ZoomDirection.LeftDown)
      {
        // 右上基準、左下方向拡大の場合
        h1 = this.tableLayoutPanel.RowStyles[1].Height + this._PitchHeight;
        w1 = this.tableLayoutPanel.ColumnStyles[0].Width + this._PitchWidth;
        h2 = this.tableLayoutPanel.RowStyles[0].Height - this._PitchHeight;
        w2 = this.tableLayoutPanel.ColumnStyles[1].Width - this._PitchWidth;
        row1 = 1;
        col2 = 1;
      }
      else if (this._Direction == ZoomDirection.LeftUp)
      {
        // 右下基準、左上方向拡大の場合
        h1 = this.tableLayoutPanel.RowStyles[0].Height + this._PitchHeight;
        w1 = this.tableLayoutPanel.ColumnStyles[0].Width + this._PitchWidth;
        h2 = this.tableLayoutPanel.RowStyles[1].Height - this._PitchHeight;
        w2 = this.tableLayoutPanel.ColumnStyles[1].Width - this._PitchWidth;
        row2 = 1;
        col2 = 1;
      }
      else
      {
        // 中央基準点、全方向拡大の場合
        h1 = this.tableLayoutPanel.RowStyles[0].Height + 0.5F;
        w1 = this.tableLayoutPanel.ColumnStyles[0].Width + 0.5F;
        h2 = this.tableLayoutPanel.RowStyles[1].Height - 0.5F;
        w2 = this.tableLayoutPanel.ColumnStyles[1].Width - 0.5F;
      }


      // コントロールの元のサイズ以下となった場合、Timerを停止し縮小を終える
      if (control.Width <= this._OriginalWidth || control.Height <= this._OriginalHeight)
      {
        this._ZoomTimer.Enabled = false;
        this._ZoomTimer.Tick -= this.ZoomInTimer_Tick;
        return;
      }

      if (this._Direction != ZoomDirection.AllSide)
      {
        this.tableLayoutPanel.RowStyles[row1].Height = h1;
        this.tableLayoutPanel.ColumnStyles[col1].Width = w1;
        this.tableLayoutPanel.RowStyles[row2].Height = h2;
        this.tableLayoutPanel.ColumnStyles[col2].Width = w2;
      }
      else
      {
        this.tableLayoutPanel.RowStyles[0].Height = h1;
        this.tableLayoutPanel.RowStyles[1].Height = h2;
        this.tableLayoutPanel.RowStyles[2].Height = h1;
        this.tableLayoutPanel.ColumnStyles[0].Width = w1;
        this.tableLayoutPanel.ColumnStyles[1].Width = w2;
        this.tableLayoutPanel.ColumnStyles[2].Width = w1;
      }
    }
    #endregion

    #region private
    /// <summary>
    /// 拡大縮小を行うためのテーブルレイアウトを構築します。
    /// </summary>
    private void CreateTebleLayout()
    {
      this.tableLayoutPanel.SuspendLayout();
      this.tableLayoutPanel.Controls.Clear();
      this.tableLayoutPanel.RowStyles.Clear();
      this.tableLayoutPanel.ColumnStyles.Clear();

      var control = CreateRoundCornerLabel();

      if (this._Direction == ZoomDirection.RightDown)
      {
        // 左上基準点、右下方向拡大の場合
        this.tableLayoutPanel.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, this._OriginalWidth));
        this.tableLayoutPanel.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, this._OriginalWidth));
        this.tableLayoutPanel.RowStyles.Add(new RowStyle(SizeType.Absolute, this._OriginalHeight));
        this.tableLayoutPanel.RowStyles.Add(new RowStyle(SizeType.Absolute, this._OriginalHeight));
        this.tableLayoutPanel.Controls.Add(control, 0, 0);
      }
      else if (this._Direction == ZoomDirection.RightUp)
      {
        // 左下基準点、右上方向拡大の場合
        this.tableLayoutPanel.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, this._OriginalWidth));
        this.tableLayoutPanel.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, this._OriginalWidth));
        this.tableLayoutPanel.RowStyles.Add(new RowStyle(SizeType.Absolute, this._OriginalHeight));
        this.tableLayoutPanel.RowStyles.Add(new RowStyle(SizeType.Absolute, this._OriginalHeight));
        this.tableLayoutPanel.Controls.Add(control, 0, 1);
      }
      else if (this._Direction == ZoomDirection.LeftDown)
      {
        // 右上基準点、左下方向拡大の場合
        this.tableLayoutPanel.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, this._OriginalWidth));
        this.tableLayoutPanel.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, this._OriginalWidth));
        this.tableLayoutPanel.RowStyles.Add(new RowStyle(SizeType.Absolute, this._OriginalHeight));
        this.tableLayoutPanel.RowStyles.Add(new RowStyle(SizeType.Absolute, this._OriginalHeight));
        this.tableLayoutPanel.Controls.Add(control, 1, 0);
      }
      else if (this._Direction == ZoomDirection.LeftUp)
      {
        // 右下基準点、左上方向拡大の場合
        this.tableLayoutPanel.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, this._OriginalWidth));
        this.tableLayoutPanel.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, this._OriginalWidth));
        this.tableLayoutPanel.RowStyles.Add(new RowStyle(SizeType.Absolute, this._OriginalHeight));
        this.tableLayoutPanel.RowStyles.Add(new RowStyle(SizeType.Absolute, this._OriginalHeight));
        this.tableLayoutPanel.Controls.Add(control, 1, 1);
      }
      else
      {
        // 中央基準点、全方向拡大の場合
        var width = 25F;
        var height = 25F;
        this.tableLayoutPanel.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, width));
        this.tableLayoutPanel.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 50F));
        this.tableLayoutPanel.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, width));
        this.tableLayoutPanel.RowStyles.Add(new RowStyle(SizeType.Percent, height));
        this.tableLayoutPanel.RowStyles.Add(new RowStyle(SizeType.Percent, 50F));
        this.tableLayoutPanel.RowStyles.Add(new RowStyle(SizeType.Percent, height));
        control.Dock = DockStyle.Fill;
        this.tableLayoutPanel.Controls.Add(control, 1, 1);
      }
      if (this._Direction != ZoomDirection.AllSide)
      {
        control.Dock = DockStyle.Fill;
      }

      this.tableLayoutPanel.ResumeLayout();
    }

    /// <summary>
    /// 角丸ラベルユーザコントロールを生成します。
    /// </summary>
    private RoundCornerLabel CreateRoundCornerLabel()
    {
      var control = new RoundCornerLabel();
      control.DisplayText = this._DisplayText;
      control.CornerColor = this._CornerColor;
      control.CornerR = this._CornerR;
      control.ForeColor = this.ForeColor;
      control.Font = this.Font;
      control.BackColor = this.tableLayoutPanel.BackColor;
      control.InnerColor = this._InnerColor;
      control.PenWidth = this._PenWidth;
      control.TextAlign = this._TextAlign;
      control.Width = (int)this._OriginalWidth;
      control.Height = (int)this._OriginalHeight;
      control.Padding = new Padding(0);

      return control;
    }

    /// <summary>
    /// 角丸ラベルユーザコントロールのプロパティを変更します。
    /// </summary>
    private void SetRoundCornerLabel()
    {
      if (this.tableLayoutPanel.Controls.Count == 0) { return; }

      var control = this.tableLayoutPanel.Controls[0] as RoundCornerLabel;

      if (control == null) { return; }

      control.DisplayText = this._DisplayText;
      control.CornerColor = this._CornerColor;
      control.CornerR = this._CornerR;
      control.ForeColor = this.ForeColor;
      control.Font = this.Font;
      control.BackColor = this.tableLayoutPanel.BackColor;
      control.InnerColor = this._InnerColor;
      control.PenWidth = this._PenWidth;
      control.TextAlign = this._TextAlign;
      control.Width = (int)this._OriginalWidth;
      control.Height = (int)this._OriginalHeight;
    }

    /// <summary>
    /// 拡大縮小時のピッチの計算を行います。
    /// </summary>
    private void CalcPitch()
    {
      // 拡大縮小を行う単位を算出する
      var gcd = Gcd(this.Width, this.Height);
      var size = CalcAspect(this.Width, this.Height);
      this._PitchHeight = size.Height;
      this._PitchWidth = size.Width;
    }

    /// <summary>
    /// 引数のサイズからアスペクト比を算出します。
    /// </summary>
    /// <param name="w">横の解像度</param>
    /// <param name="h">縦の解像度</param>
    /// <returns>アスペクト比</returns>
    private SizeF CalcAspect(int w, int h)
    {
      // 最大公約数を求める
      var gcd = Gcd(w, h);
      if (gcd == 1) { gcd = 100; }

      var result = new SizeF();
      result.Width = (float)this.Width / (float)gcd;
      result.Height = (float)this.Height / (float)gcd;
      return result;
    }

    /// <summary>
    /// 最大公約数を求めます。
    /// </summary>
    /// <param name="w">横の解像度</param>
    /// <param name="h">縦の解像度</param>
    /// <returns></returns>
    private int Gcd(int w, int h)
    {
      if (w < h)
      {
        return Gcd(h, w);
      }
      while (h != 0)
      {
        var remainder = w % h;
        w = h;
        h = remainder;
      }
      return w;
    }
    #endregion
  }
}

 Windowsフォーム アプリケーションでも工夫と頑張りでアニメーションっぽい処理もそれなりにできますが、やっぱりWPFの方が楽ですね…。
 枯れてきたWindowsフォーム アプリケーションを使いたいって要件もまだそこそこありますので、参考になればと思います。

【Android】Ripple Effectをコードで適応【Material Design】

今回はちょっとした小ネタになります。

Androidでボタンなどを押したときに出てくる波紋状のエフェクトですが、通常なら以下のようにxmlで記述し、backgroundに設定してあげることで実現することができます。

/values/colors.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <color name="btn_color">#ffffaab3</color>
    <color name="btn_highlight_color">#ff4081</color>
</resources>


/drawable/btn_ripple.xml

<?xml version="1.0" encoding="utf-8"?>
    <ripple xmlns:android="http://schemas.android.com/apk/res/android" android:color="@color/btn_highlight_color">
    <item android:drawable="@color/btn_color" />
</ripple>

/layout/activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <Button
        android:id="@+id/btn1"
        android:layout_width="match_parent"
        android:layout_height="30dp"
        android:background="@drawable/btn_ripple"
        android:text="Hello World!" />
</RelativeLayout>

今回はxmlを使用せずにコードでRipple Effectを実現する方法を書いていきます。
コード自体は簡単で、以下のようにコードを書いてeffectを適用させたいViewを渡してあげるだけです。

RippleEffect.java

public class RippleEffect {
    private static ColorStateList getPressedColorSelector(int normalColor, int pressedColor) {
        return new ColorStateList(new int[][] {
                new int[]{android.R.attr.state_pressed},
                new int[]{android.R.attr.state_focused},
                new int[]{android.R.attr.state_activated},
                new int[]{}
        }, new int[] {
                pressedColor,
                pressedColor,
                pressedColor,
                normalColor
        });
    }

    public static void setRippleDrawable(View v, int normalColor, int pressedColor) {
        RippleDrawable ripple =  new RippleDrawable(getPressedColorSelector(normalColor, pressedColor), new ColorDrawable(normalColor), null);
        ripple.setColor(ColorStateList.valueOf(pressedColor));
        v.setBackground(ripple);
    }
}



MainActivity.java

public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Button btn = (Button)findViewById(R.id.btn1);
        RippleEffect.setRippleDrawable(btn, getResources().getColor(R.color.btn_color), getResources().getColor(R.color.btn_highlight_color));
    }
}

基本的にはxmlで記述し、backgroundに設定してあげるやり方でいいと思いますが、動的に色を変えたい時などにはお勧めです。

【Swift 3】煩わしいString操作を簡単にする

Stringの操作についてObj-C時代から使い慣れているNSStringと比べ、随分使い勝手が悪いなーと感じることが多いです。

Qiitaになぜそうなっているのかという詳しい解説がありますが、使う上で不便なことには変わりないので、Extensionを書きました。

qiita.com

コード

extension String {
    func substring(to index: Int) -> String {
        return substring(to: self.index(self.startIndex, offsetBy: abs(index)))
    }
    func substring(from index: Int) -> String {
        return substring(from: self.index(self.endIndex, offsetBy: -abs(index)))
    }
    func substring(with range: NSRange) -> String {
        return substring(with: self.range(from: range)!)
    }
    func replacingCharacters(in range: NSRange, with text: String) -> String {
        return self.replacingCharacters(in: self.range(from: range)!, with: text)
    }
}

NSRangeRange<String.Index>の変換を行っている部分ですがStack Overflowから頂きました。

stackoverflow.com

extension String {
    func range(from nsRange: NSRange) -> Range<String.Index>? {
        guard let from16 = utf16.index(utf16.startIndex, offsetBy: nsRange.location, limitedBy: utf16.endIndex),
            let to16 = utf16.index(from16, offsetBy: nsRange.length, limitedBy: utf16.endIndex),
            let from = from16.samePosition(in: self),
            let to = to16.samePosition(in: self)
            else { return nil }
        return from ..< to
    }
}

【C#】Windowsフォーム アプリケーションで角丸コーナーのラベル

 今回はWindowsフォーム アプリケーションで角丸コーナーラベルを作成します。
 ピクチャーボックス上にレンダリングして角丸コーナーのラベルっぽいものにしています。ピクチャーボックスではControl.DoubleBuffered プロパティが使えなかったため、DoubleBufferクラスを実装してダブルバッファリングを行っています。

using System;
using System.Drawing;
using System.Windows.Forms;

namespace RoundCornerLabelSample
{
  /// <summary>
  /// ダブルバッファクラス
  /// </summary>
  public class DoubleBuffer : IDisposable
  {
    #region メンバ変数
    /// <summary>
    /// ダブルバッファリングオブジェクト
    /// </summary>
    private BufferedGraphics _Buffer = null;

    /// <summary>
    /// バッファリングを行うコントロール
    /// </summary>
    private Control _Control = null;
    #endregion

    #region プロパティ
    /// <summary>
    /// グラフィックオブジェクト
    /// </summary>
    public Graphics Graphics
    {
      get
      {
        return this._Buffer.Graphics;
      }
    }
    #endregion

    #region コンストラクタ
    /// <summary>
    /// クラスの新しいインスタンスを初期化します。
    /// </summary>
    /// <param name="control">バッファリングを行うコントロール</param>
    public DoubleBuffer(Control control)
    {
      this.Dispose();

      var currentContext = BufferedGraphicsManager.Current;
      this._Buffer = currentContext.Allocate(control.CreateGraphics(), control.DisplayRectangle);

      if (this._Control != null)
      {
        this._Control.Paint += new System.Windows.Forms.PaintEventHandler(this.Paint);
      }
    }
    #endregion

    #region デストラクタ
    /// <summary>
    /// デストラクタ
    /// </summary>
    ~DoubleBuffer()
    {
      Dispose();
    }
    #endregion

    #region publicメソッド
    /// <summary>
    /// 解放処理を行います。
    /// </summary>
    public void Dispose()
    {
      if (this._Buffer != null)
      {
        this._Buffer = null;
      }
      if (this._Control != null)
      {
        this._Control.Paint -= new System.Windows.Forms.PaintEventHandler(this.Paint);
      }
    }

    /// <summary>
    /// 更新を行います。
    /// </summary>
    public void Refresh()
    {
      if (this._Buffer != null)
      {
        this._Buffer.Render();
      }
    }
    #endregion

    #region privateメソッド
    /// <summary>
    /// 描画処理を行います。
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    private void Paint(object sender, PaintEventArgs e)
    {
      Refresh();
    }
    #endregion
  }
}

 角丸コーナーラベルの実装コードです。ラベル内の文字が右方向にずれるためレンダリング時に-3位置をずらしています。

namespace RoundCornerLabelSample
{
  partial class RoundCornerLabel
  {
    /// <summary> 
    /// 必要なデザイナー変数です。
    /// </summary>
    private System.ComponentModel.IContainer components = null;

    /// <summary> 
    /// 使用中のリソースをすべてクリーンアップします。
    /// </summary>
    /// <param name="disposing">マネージ リソースが破棄される場合 true、破棄されない場合は false です。</param>
    protected override void Dispose(bool disposing)
    {
      if (disposing && (components != null))
      {
        components.Dispose();
      }
      base.Dispose(disposing);
    }

    #region コンポーネント デザイナーで生成されたコード

    /// <summary> 
    /// デザイナー サポートに必要なメソッドです。このメソッドの内容を 
    /// コード エディターで変更しないでください。
    /// </summary>
    private void InitializeComponent()
    {
      this.pic = new System.Windows.Forms.PictureBox();
      ((System.ComponentModel.ISupportInitialize)(this.pic)).BeginInit();
      this.SuspendLayout();
      // 
      // pic
      // 
      this.pic.BackColor = System.Drawing.Color.Transparent;
      this.pic.Dock = System.Windows.Forms.DockStyle.Fill;
      this.pic.Location = new System.Drawing.Point(0, 0);
      this.pic.Margin = new System.Windows.Forms.Padding(0);
      this.pic.Name = "pic";
      this.pic.Size = new System.Drawing.Size(200, 150);
      this.pic.TabIndex = 0;
      this.pic.TabStop = false;
      // 
      // RoundCornerLabel
      // 
      this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 12F);
      this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
      this.Controls.Add(this.pic);
      this.Margin = new System.Windows.Forms.Padding(0);
      this.Name = "RoundCornerLabel";
      this.Size = new System.Drawing.Size(200, 150);
      ((System.ComponentModel.ISupportInitialize)(this.pic)).EndInit();
      this.ResumeLayout(false);

    }

    #endregion

    private System.Windows.Forms.PictureBox pic;
  }
}
using System;
using System.ComponentModel;
using System.Drawing;
using System.Windows.Forms;
using System.Drawing.Drawing2D;

namespace RoundCornerLabelSample
{
  /// <summary>
  /// 角丸コーナーラベルユーザコントロール
  /// </summary>
  public partial class RoundCornerLabel : UserControl
  {
    #region メンバ変数
    /// <summary>
    /// ダブルバッファクラス
    /// </summary>
    private DoubleBuffer _DoubleBuffer = null;

    /// <summary>
    /// コントロールに関連付けられたテキスト
    /// </summary>
    private string _DisplayText = "";

    /// <summary>
    /// 文字列の配置
    /// </summary>
    private ContentAlignment _TextAlign = ContentAlignment.MiddleCenter;

    /// <summary>
    /// コーナーの角丸のサイズ(直径)
    /// </summary>
    private int _CornerR = 10;

    /// <summary>
    /// コーナーの線の太さ
    /// </summary>
    private int _PenWidth = 5;

    /// <summary>
    /// コーナーの線の色
    /// </summary>
    private Color _CornerColor = Color.Blue;

    /// <summary>
    /// 矩形内の背景色
    /// </summary>
    private Color _InnerColor = SystemColors.Window;
    #endregion

    #region プロパティ
    /// <summary>
    /// コントロールに関連付けられたテキスト
    /// </summary>
    [Browsable(true)]
    [Category("表示")]
    [Description("コントロールに関連付けられたテキストです。")]
    public string DisplayText
    {
      get
      {
        return this._DisplayText;
      }
      set
      {
        this._DisplayText = value;
        Refresh();
      }
    }

    /// <summary>
    /// コントロールに表示される文字列の配置
    /// </summary>
    [Browsable(true)]
    [Category("表示")]
    [Description("コントロールに表示される文字列の配置です。")]
    public ContentAlignment TextAlign
    {
      get
      {
        return this._TextAlign;
      }
      set
      {
        this._TextAlign = value;
        Refresh();
      }
    }

    /// <summary>
    /// 角の丸さ
    /// </summary>
    [Browsable(true)]
    [Category("表示")]
    [Description("角の丸さを指定します。(半径)")]
    public int CornerR
    {
      get
      {
        return (int)(this._CornerR / 2);
      }
      set
      {
        if (0 < value)
        {
          this._CornerR = value * 2;
        }
        else
        {
          // 0以下が設定された場合、強制的に1にする
          this._CornerR = 1;
        }

        //RenewPadding();
        Refresh();
      }
    }

    /// <summary>
    /// 枠線の色
    /// </summary>
    [Browsable(true)]
    [Category("表示")]
    [Description("外枠の色を指定します。")]
    public Color CornerColor
    {
      get
      {
        return this._CornerColor;
      }
      set
      {
        this._CornerColor = value;
        Refresh();
      }
    }

    /// <summary>
    /// 枠線の幅を指定します。
    /// </summary>
    [Browsable(true)]
    [Category("表示")]
    [Description("枠線の太さを指定します。")]
    public int PenWidth
    {
      get
      {
        return this._PenWidth;
      }
      set
      {
        if (0 <= value)
        {
          this._PenWidth = value;
        }
        else
        {
          // 0以下が指定された場合、強制的に0にする
          this._PenWidth = 0;
        }

        //RenewPadding();
        Refresh();
      }
    }

    /// <summary>
    /// コントロールの矩形内の背景色
    /// </summary>
    [Browsable(true)]
    [Category("表示")]
    [Description("コントロールの矩形内の背景色を指定します。")]
    public Color InnerColor
    {
      get
      {
        return this._InnerColor;
      }
      set
      {
        this._InnerColor = value;
        Refresh();
      }
    }
    #endregion

    #region コンストラクタ
    /// <summary>
    /// クラスの新しいインスタンスを初期化します。
    /// </summary>
    public RoundCornerLabel()
    {
      InitializeComponent();

      // コントロールのサイズが変更された時にPaintイベントを発生させる
      SetStyle(ControlStyles.ResizeRedraw, true);
      SetStyle(ControlStyles.SupportsTransparentBackColor, true);
      // ピクチャボックスでダブルバッファを行うようにインスタンス生成
      this._DoubleBuffer = new DoubleBuffer(this.pic);

      this.BackColor = Color.Transparent;
    }
    #endregion

    #region override
    /// <summary>
    /// PaintBackground イベントを発生させます。
    /// </summary>
    /// <param name="e">イベント引数</param>
    protected override void OnPaintBackground(PaintEventArgs e)
    {
      // バックグランドの描画を行うとWhiteの挿入がされるため
      // 何もしないようにする
    }

    /// <summary>
    /// Load イベントを発生させます。
    /// </summary>
    /// <param name="e">イベント引数</param>
    protected override void OnLoad(EventArgs e)
    {
      base.OnLoad(e);
    }

    /// <summary>
    /// Paint イベントを発生させます。
    /// </summary>
    /// <param name="e">イベント引数</param>
    protected override void OnPaint(PaintEventArgs e)
    {
      Draw(e.Graphics);
    }
    #endregion

    #region private
    /// <summary>
    /// 描画を行います。
    /// </summary>
    /// <param name="g">グラフィックオブジェクト</param>
    private void Draw(Graphics g)
    {
      // 描画品質設定
      g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
      g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAlias;
      g.PixelOffsetMode = System.Drawing.Drawing2D.PixelOffsetMode.HighQuality;

      // 背景用のImageオブジェクトを作成する
      var backrect = new Rectangle(0, 0, this.Width, this.Height);
      var backColor = this.Parent != null ? this.Parent.BackColor : SystemColors.Control;
      using (var brush = new SolidBrush(backColor))
      {
        g.FillRectangle(brush, backrect);
      }

      using (var cornerPen = new Pen(this._CornerColor, this._PenWidth))
      {
        // 変数初期化
        var harfPenWidth = GetHarfPenWidth();

        // ペンとブラシ初期化
        var offset = this._PenWidth;
        var rect = new Rectangle(offset, offset, this.Width, this.Height);
        using (var brush = new SolidBrush(this._InnerColor))
        using (var graphPath = new GraphicsPath())
        {
          graphPath.AddArc(harfPenWidth, harfPenWidth, this._CornerR, this._CornerR, 180, 90);
          graphPath.AddArc(this.Width - this._CornerR - harfPenWidth, harfPenWidth, this._CornerR, this._CornerR, 270, 90);
          graphPath.AddArc(this.Width - this._CornerR - harfPenWidth, this.Height - this._CornerR - harfPenWidth, this._CornerR, this._CornerR, 0, 90);
          graphPath.AddArc(harfPenWidth, this.Height - this._CornerR - harfPenWidth, this._CornerR, this._CornerR, 90, 90);
          graphPath.CloseFigure();

          // パス塗り
          g.FillPath(brush, graphPath);

          // ペンの太さが1以上なら外枠描画
          if (0 < this._PenWidth)
          {
            g.DrawPath(cornerPen, graphPath);
          }
        }

        // テキストが設定されている場合は文字を書く
        var size = new Size(this.Width - offset * 2, this.Height - offset * 2);
        var fontSize = CalcFontSize(this._DisplayText, size, g);
        if (fontSize <= 0) { fontSize = 0.1F; }

        var font = new Font(this.Font.Name, fontSize);
        var stringFormat = new StringFormat();
        var num = (Int32)System.Math.Log((Double)this._TextAlign, 2);
        stringFormat.LineAlignment = (StringAlignment)(num / 4);
        stringFormat.Alignment = (StringAlignment)(num % 4);

        // 文字描画用のブラシを生成する
        using (var foreColorBrush = new SolidBrush(this.ForeColor))
        {
          // 微妙に右にずれるため-3して補正する
          rect.X = rect.X - 3;
          g.DrawString(this._DisplayText, font, foreColorBrush, rect, stringFormat);
        }
      }
    }

    /// <summary>
    /// Paddingサイズを更新します。
    /// </summary>
    private void RenewPadding()
    {
      var harfCornerR = (int)(this._CornerR / 2);
      var adjust = (int)(System.Math.Cos(45 * System.Math.PI / 180) * harfCornerR);
      this.Padding = new Padding(harfCornerR + this._PenWidth + 2 - adjust);
    }

    /// <summary>
    /// コーナー線の幅の1/2の値を取得します。
    /// </summary>
    /// <returns>コーナー線の幅の1/2の値</returns>
    private int GetHarfPenWidth()
    {
      return (int)System.Math.Floor((double)(this._PenWidth) / 2 + 0.5);
    }

    /// <summary>
    /// 指定されたグラフィックオブジェクトに描画する最適なフォントサイズを算出します。
    /// </summary>
    /// <param name="str">出力する文字列</param>
    /// <param name="size">サイズ</param>
    /// <param name="g">描画するグラフィックオブジェクト</param>
    /// <returns>フォントサイズ</returns>
    private float CalcFontSize(string str, Size size, Graphics g)
    {
      var s = new SizeF(0.1F, 0.1F);
      var a = 0.1F;
      var b = 0.1F;

      if (!string.IsNullOrEmpty(str))
      {
        s = g.MeasureString(str, new Font(this.Font.Name, 1));
        a = (size.Width / s.Width);
        b = (size.Height / s.Height);
      }
      return (a < b) ? a : a;
    }
    #endregion
  }
}

 WPFの場合、簡単に実装できますがWindowsフォーム アプリケーションだとかなり面倒ですね…。

【Xamarin.Android】XamarinプロジェクトのFabric登録【クラッシュレポート】

最近Javaのコード見るとうわっって思うくらいXamarinばっかり触っています。
今回もXamarinネタを頑張って書いていきます。

クラッシュレポート

アプリを開発しているとクラッシュレポートがほしくなってきますよね!
AndroidStudioで開発しているときはFabricを使用していたので、Xamarinでもそれでいいかなと思っていました。
しかし、なんとXamarinはFabric対応していないのです!!!

ちょっと調べてみたところ、Xamarinでクラッシュレポートを行う際はHockeyApp(元Xamarin Insightsという製品が使われていたよう)を使うのがいいみたいです。
だけど、今まで作ってきたアプリはFabricで管理してて、今回作るアプリだけHockeyAppかー、、、
どうせなら一緒にまとめたいなー、、、

と思い、XamarinでもFabricが使えるように頑張ってみました!笑
今回はその手順のご紹介です。(特にこだわりがなければHockeyAppにしましょう!)

Fabric利用手順

XamarinでFabricを利用するために、大きく以下のことを行います。

  1. Xamarinプロジェクト作成(パッケージ名設定)
  2. AndroidStudioでXamarinと同じパッケージ名でプロジェクトを作成
  3. AndroidStudioからアプリをFabricに登録
  4. Xamarin側でFabricの設定

以下詳細に記載

  1. 通常通りXamarinプロジェクトを作成

  2. プロジェクトの設定からパッケージ名を設定

  3. AndroidStudioでXamarinと同じパッケージ名でプロジェクトを作成

  4. AndroidStudioでFabric(Crashlytics)の登録
    Fabricの登録は以下のページを参考にしてください。
    www.dentare.net
    dev.classmethod.jp

  5. 一通りAndroidStudio側でFabricの設定を行ったら、一度アプリを実行してFabricにプロジェクトを登録
    (登録するだけなので、Hello worldとかでいいです)
    その後Fabricのサイトにログインし、先ほど実行したアプリが登録されていることを確認してください。

  6. Fabric公式サイトまたはFabricに登録するために作成したAndroidStudioのプロジェクトからAPIKEYを取得
    (Xamarin側で使用します)

  7. XamarinのNugetでhttps://github.com/azchohfi/Crashlytics.Droidを導入
    READMEに書いてある通りに、AndroidMajnifestとString.xml、ActivityのOnCreateを修正します!

  8. 修正後アプリを実行して、クラッシュしたときにFabricに送られていれば成功です!
    (クラッシュ用のコードを書いて確認すると楽です。)



正直ここまで面倒な手順を踏んでまでFabricに登録するメリットは少ないかもしれません、、、

今回はXamarinからFabricに登録する手段が分からなかったので、AndroidStudioでFabricに登録だけして、その後はXamarinから使用する方法を試してみました。
どうしてもFabricを使いたいって場合は試してみてください。

Photoshop小技集その壱 ~ イラストからキャラだけを可能な限り早く楽に切り抜く

ども、デザイナーです。

今回はタイトル通り画像の切り抜きを紹介します。

はじめに

画像の切り抜きはデザイナー必須の技術ですが、1口に切り抜きと言っても本当に色んな方法がありますよね。

(僕個人は最終的なクオリティのことを考えるとペンツールなどで細かく範囲を選択するなどした方が良いと思ってます。 矩形もパスとして保存されるしね。)

今回ご紹介するのは細かいクオリティよりもスピードが重視される時に僕がよく使う切り抜きの方法です。

f:id:atamo_dev:20170131184052p:plain

今回は上図のような画像からキャラクターの部分を切り抜きます。

1.レイヤーコピー

まずは今回使うツール!

自動選択ツールを使います。

f:id:atamo_dev:20170131184058p:plain

このまま普通に背景のピンクの部分を自動選択ツールで選択すると図のようにキャラの周囲にある白いモヤの部分が邪魔して思うようにキャラだけを切り抜くことができません。

f:id:atamo_dev:20170131184100p:plain

なのでまず、 レイヤーをコピーしましょう。(ショートカットキーCtrl + j)

f:id:atamo_dev:20170131184057p:plain

2.画像に無茶な補正をかけてコントラストを強める

増えた方のレイヤーが選択されてることを確認してイメージ色調補正レベル補正でレベル補正ダイアログを開きます。

f:id:atamo_dev:20170131184059p:plain

そんでもって、レベル補正ダイアログを操作し、めちゃくちゃコントラストを高めます。 設定は画像によって変わるので各自調節してください。

f:id:atamo_dev:20170131184102p:plain

レベル補正を反映させた結果がこれです。

f:id:atamo_dev:20170131184053p:plain

3.選択範囲をとってから元画像に戻って削除

ここで選択ツールを使用します。 f:id:atamo_dev:20170131184103p:plain 上図のような設定にしてから、赤くなった背景部分を選択します。

f:id:atamo_dev:20170131184101p:plain

選択範囲が選択された状態で、元のレイヤーに戻りdeleteキーを押します。

f:id:atamo_dev:20170131184054p:plain

4.コピーしたレイヤーの削除

仕上げにコピーしたレイヤーを削除して、完成!早いでしょ?

まとめ

まとめです。

1.レイヤーコピー

2.画像に無茶な補正をかけてコントラストを強める

3.選択範囲をとってから元画像に戻って削除

4.コピーしたレイヤーの削除

全く同じ手順で次のような写真も簡単に切り抜けます。

f:id:atamo_dev:20170131184051j:plain

画像は以下から頂きました。

www.photo-ac.com

f:id:atamo_dev:20170131184104p:plain

今回大事なのは選択範囲をとるためだけの新しいレイヤーをコピーして作る部分です。

選択範囲さえとったらコピーしたレイヤーは捨てるので使い捨てです。

こんな感じでレイヤーを操作すると色んな作業が一気に楽になります。

今回ご紹介した内容は、たくさんある手段の内のひとつですので飽くまで参考程度に見ていただけると幸いですん。んじゃまた!

【watchOS】Apple Watchで位置情報取得

Apple Watch seriese2から単体で位置情報を取得できるようになりました。

ということで、今回はApple Watchで位置情報を取得してみました。

コード

まずはコードをサクッと書いてみましょう プロジェクトを作成し、ターゲットを追加、WatchKit Appを選択しましょう。

そして追加されたInterfaceController.swiftを編集します。


import WatchKit
import Foundation
import CoreLocation

class InterfaceController: WKInterfaceController, CLLocationManagerDelegate {

    @IBOutlet var latitudeLabel: WKInterfaceLabel!      // InterfaceBuiderから追加
    @IBOutlet var longitudeLabel: WKInterfaceLabel!     // InterfaceBuiderから追加
    private var locationManager: CLLocationManager = CLLocationManager()
    private var currentLocation: CLLocation!
    
    override func willActivate() {
        super.willActivate()
        locationManager.desiredAccuracy = kCLLocationAccuracyBest
        locationManager.distanceFilter = 1000
        
        locationManager.delegate = self
        
        let status = CLLocationManager.authorizationStatus()
        if(status == CLAuthorizationStatus.notDetermined) {
            self.locationManager.requestWhenInUseAuthorization()
        }
        self.locationManager.startUpdatingLocation()
    }
    
    override func didDeactivate() {
        super.didDeactivate()
    }

    // MARK: - CLLocationManagerDelegate
    func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
        
    }
    
    func locationManager(_ manager: CLLocationManager, didUpdateLocations locations:[CLLocation]) {
        
        let locationData = locations.last as CLLocation!
        self.currentLocation = locationData
        latitudeLabel.setText(String(self.currentLocation.coordinate.latitude))
        longitudeLabel.setText(String(self.currentLocation.coordinate.longitude))
        
        self.locationManager.stopUpdatingLocation()
    }
    
    private func locationManager(manager: CLLocationManager, didChangeAuthorizationStatus status:CLAuthorizationStatus) {
        if (status == .notDetermined) {

            if (self.locationManager.responds(to: #selector(CLLocationManager.requestWhenInUseAuthorization))) {
                self.locationManager.requestWhenInUseAuthorization()
            }
        }
    }
}

確認

AppleWatcとペアリングしているiPhone機内モードにし、AppleWatc単体で位置情報を取得できるか確認します。

iPhoneとの接続時と比べ、若干精度が落ちるようですが、無事に位置情報の取得が確認できました。

MapViewを追加すればWatch内でMapの表示もできるようです。

【C#】Windowsフォーム アプリケーションで動画再生ユーザコントロール

 引き続きWindowsフォーム アプリケーション関連のメモです。
今回はWindowsフォーム アプリケーションでの動画再生についてです。一番簡単な方法はWindows Media Playerを利用することですが、あまりカスタマイズが出来ないため今回はDirectShowを使用します。

 環境によってはActiveMovie control type libraryが参照できないときがあります。その場合、以下の方法を試してください。

 1. レジストリエディタを起動し「HKEY_CLASSES_ROOT\TypeLib\{56A868B0-0AD4-11CE-B03A-0020AF0BA770}\1.0」を開く。
 2. データの値が「quartz.dll」となっているのを確認して、「C:\Windows\System32\quartz.dll」の形式に変更。
 3. Visual Studioで参照の追加を行い、ActiveMovie control type libraryを参照します。

namespace MoviePlayerSample
{
  partial class MoviePlayer
  {
    /// <summary>
    /// 必要なデザイナー変数です。
    /// </summary>
    private System.ComponentModel.IContainer components = null;

    /// <summary>
    /// 使用中のリソースをすべてクリーンアップします。
    /// </summary>
    /// <param name="disposing">マネージ リソースが破棄される場合 true、破棄されない場合は false です。</param>
    protected override void Dispose(bool disposing)
    {
      if (disposing && (components != null))
      {
        components.Dispose();
      }
      base.Dispose(disposing);
    }

    #region コンポーネント デザイナーで生成されたコード

    /// <summary>
    /// デザイナー サポートに必要なメソッドです。このメソッドの内容を
    /// コード エディターで変更しないでください。
    /// </summary>
    private void InitializeComponent()
    {
      this.picBackground = new System.Windows.Forms.PictureBox();
      ((System.ComponentModel.ISupportInitialize)(this.picBackground)).BeginInit();
      this.SuspendLayout();
      //
      // picBackground
      //
      this.picBackground.BackColor = System.Drawing.Color.MistyRose;
      this.picBackground.Dock = System.Windows.Forms.DockStyle.Fill;
      this.picBackground.Location = new System.Drawing.Point(0, 0);
      this.picBackground.Margin = new System.Windows.Forms.Padding(0);
      this.picBackground.Name = "picBackground";
      this.picBackground.Size = new System.Drawing.Size(431, 190);
      this.picBackground.TabIndex = 0;
      this.picBackground.TabStop = false;
      //
      // MoviePlayer
      //
      this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 12F);
      this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
      this.BackColor = System.Drawing.Color.OrangeRed;
      this.Controls.Add(this.picBackground);
      this.Margin = new System.Windows.Forms.Padding(0);
      this.Name = "MoviePlayer";
      this.Size = new System.Drawing.Size(431, 190);
      ((System.ComponentModel.ISupportInitialize)(this.picBackground)).EndInit();
      this.ResumeLayout(false);

    }

    #endregion

    private System.Windows.Forms.PictureBox picBackground;

  }
}


 再生不可な動画を指定した場合、例外が発生しますのでエラー時の処理を追加することをお勧めします。後、Playメソッドで動画の拡張子チェックなども行うと良いでしょう。
またWndProcメソッドで動画再生の終了メッセージを補足したとき、ループ再生を行っています。そのまま停止させたい場合は改良してください。
早送り、巻き戻しには対応していません。

using QuartzTypeLib;
using System;
using System.ComponentModel;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.IO;
using System.Runtime.InteropServices;
using System.Windows.Forms;

namespace MoviePlayerSample
{
  /// <summary>
  /// 動画再生ユーザコントロール
  /// </summary>
  public partial class MoviePlayer : UserControl
  {
    #region 定数
    /// 動画を子として描画する値を表す定数
    /// </summary>
    private const int WS_CHILD = 0x40000000;
    /// <summary>
    /// 子ウィンドウを互いに重ならないようにクリップする値を表す定数
    /// </summary>
    private const int WS_CLIPSIBLINGS = 0x04000000;
    /// <summary>
    /// すべてのデータのレンダリングをし終えた値を表す定数
    /// </summary>
    private const int EC_COMPLETE = 0x01;
    /// <summary>
    /// ユーザーが再生を強制終了した値を表す定数
    /// </summary>
    private const int EC_USERABORT = 0x02;
    /// <summary>
    /// アプリケーション定義のメッセージを定義する値を表す定数
    /// </summary>
    private const int WM_APP = 0x8000;
    /// <summary>
    /// フィルタグラフイベントのメッセージ取得に使用するIDを表す定数
    /// </summary>
    private const int WM_GRAPHNOTIFY = WM_APP + 1;
    #endregion

    #region メンバ変数
    /// <summary>
    /// フィルタグラフマネージャ
    /// </summary>
    private FilgraphManager _FilterGraph = null;
    /// <summary>
    /// メディアコントロールインターフェース
    /// </summary>
    private IMediaControl _MediaControl = null;
    /// <summary>
    /// ビデオウィンドウインターフェース
    /// </summary>
    private IVideoWindow _VideoWindow = null;
    /// <summary>
    /// メディアイベントインターフェース
    /// </summary>
    private IMediaEventEx _MediaEventEx = null;
    /// <summary>
    /// ベーシックビデオインターフェース
    /// </summary>
    private IBasicVideo _BasicVideo = null;
    /// <summary>
    /// ベーシックオーディオインターフェース
    /// </summary>
    private IBasicAudio _BasicAudio = null;
    /// <summary>
    /// メディアポジションインターフェース
    /// </summary>
    private IMediaPosition _MediaPosition = null;
    /// <summary>
    /// 再生を行う動画ファイルのパス
    /// </summary>
    private string _Source = String.Empty;
    #endregion

    #region プロパティ
    /// <summary>
    /// 再生する動画のファイルパスを取得または設定します。
    /// </summary>
    [Browsable(true)]
    [Category("動作")]
    [Description("再生する動画のファイルパスを取得または設定します。")]
    public string Source
    {
      get
      {
        return this._Source;
      }
      set
      {
        if (this._Source != value)
        {
          this._Source = value;
          Play();
        }
      }
    }

    /// <summary>
    /// 背景に画像を表示する場合のファイルパスを取得または設定します。
    /// </summary>
    [Browsable(true)]
    [Category("動作")]
    [Description("背景に画像を表示する場合のファイルパスを取得または設定します。")]
    public string Background
    {
      get
      {
        return this.picBackground.ImageLocation;
      }
      set
      {
        if (this.picBackground.ImageLocation != value)
        {
          this.picBackground.ImageLocation = value;
          ImageCorrection();
        }
      }
    }
    #endregion

    #region コンストラクタ
    /// <summary>
    /// クラスの新しいインスタンスを初期化します。
    /// </summary>
    public MoviePlayer()
    {
      InitializeComponent();
    }
    #endregion

    #region イベント
    /// <summary>
    /// ユーザコントロールがアンロードされたときに発生します。
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    private void OnUnload(object sender, EventArgs e)
    {
      // COMオブジェクトの初期化
      InitializeControl();
    }
    #endregion

    #region publicメソッド
    /// <summary>
    /// 動画を再生します。
    /// </summary>
    /// <param name="filePath"></param>
    public void Play()
    {
      // 再生する動画ファイルのパスが設定されていない場合、処理から抜ける
      if (String.IsNullOrEmpty(this._Source)) { return; }

      // 再生する動画ファイルが存在しない場合、処理から抜ける
      if (!File.Exists(this._Source)) { return; }

      PlayMovie();
    }

    /// <summary>
    /// 音量を上げます。
    /// </summary>
    public void VolumeUp()
    {
      if (this._BasicAudio != null && this._BasicAudio.Volume != 0)
      {
        this._BasicAudio.Volume += 100;
      }
    }

    /// <summary>
    /// 音量を下げます。
    /// </summary>
    public void VolumeDown()
    {
      if (this._BasicAudio != null && this._BasicAudio.Volume != -10000)
      {
        this._BasicAudio.Volume -= 100;
      }
    }

    /// <summary>
    /// 一時停止を解除します。
    /// </summary>
    public void Unpause()
    {
      if (this._MediaControl != null)
      {
        // グラフの一時停止を解除する
        this._MediaControl.Run();
      }
    }

    /// <summary>
    /// 動画の再生を一時停止します。
    /// </summary>
    public void Pause()
    {
      if (this._MediaControl != null)
      {
        // グラフを一時停止する
        this._MediaControl.Stop();
      }
    }
    #endregion

    #region protectedメソッド
    /// <summary>
    /// Load イベントを発生させます。
    /// </summary>
    /// <param name="e">イベントの引数</param>
    protected override void OnLoad(EventArgs e)
    {
      this.Disposed += OnUnload;

      base.OnLoad(e);
    }

    /// <summary>
    /// Resize イベントを発生させます。
    /// </summary>
    /// <param name="e">イベントの引数</param>
    protected override void OnResize(EventArgs e)
    {
      // 背景画像のサイズを変更します。
      ImageCorrection();

      if (this._VideoWindow == null) { return; }

      // 動画のサイズの取得
      var size = CalcDisplaySize();

      // 表示位置のオフセットを取得
      var point = CalcDisplayPoint(size);

      // VideWindowを動画のサイズに設定
      this._VideoWindow.SetWindowPosition(point.X, point.Y, size.X, size.Y);

      base.OnResize(e);
    }

    /// <summary>
    /// Windowsメッセージ処理のオーバーライド
    /// </summary>
    /// <param name="m"></param>
    protected override void WndProc(ref Message m)
    {
      int eventCode;
      int lparam1;
      int lparam2;

      if (m.Msg == WM_GRAPHNOTIFY)
      {
        // イベントをキューから取得する
        // while文でキューから全てのイベントを取得する
        while (true)
        {
          try
          {
            this._MediaEventEx.GetEvent(out eventCode, out lparam1, out lparam2, 0);
            this._MediaEventEx.FreeEventParams(eventCode, lparam1, lparam2);

            // ストリーム終了または中断
            if ((eventCode == EC_COMPLETE) || (eventCode == EC_USERABORT))
            {
              this._MediaPosition.CurrentPosition = 0;
              this._MediaControl.Run();
            }
          }
          catch (Exception)
          {
            break;
          }
        }
      }

      base.WndProc(ref m);
    }
    #endregion

    #region privateメソッド
    /// <summary>
    /// コントロールを初期化します。
    /// </summary>
    private void InitializeControl()
    {
      // 必要な場合は、動画の停止と画面の初期化
      if (this._MediaControl != null)
      {
        this._MediaControl.Stop();
      }

      if (this._MediaEventEx != null)
      {
        this._MediaEventEx.SetNotifyWindow(0, 0, 0);
      }

      if (this._VideoWindow != null)
      {
        this._VideoWindow.Visible = 0;
        this._VideoWindow.Owner = 0;
      }

      // 各COMオブジェクト(インターフェース)のRelease
      if (this._FilterGraph != null)
      {
        Marshal.ReleaseComObject(this._FilterGraph);
        this._FilterGraph = null;
      }

      if (this._BasicVideo != null)
      {
        Marshal.ReleaseComObject(this._BasicVideo);
        this._BasicVideo = null;
      }

      if (this._BasicAudio != null)
      {
        Marshal.ReleaseComObject(this._BasicAudio);
        this._BasicAudio = null;
      }

      if (this._MediaControl != null)
      {
        Marshal.ReleaseComObject(this._MediaControl);
        this._MediaControl = null;
      }

      if (this._MediaPosition != null)
      {
        Marshal.ReleaseComObject(this._MediaPosition);
        this._MediaPosition = null;
      }

      if (this._MediaEventEx != null)
      {
        Marshal.ReleaseComObject(this._MediaEventEx);
        this._MediaEventEx = null;
      }

      if (this._VideoWindow != null)
      {
        Marshal.ReleaseComObject(this._VideoWindow);
        this._VideoWindow = null;
      }
    }

    /// <summary>
    /// 動画を再生します。
    /// </summary>
    private void PlayMovie()
    {
      // COMオブジェクトの初期化
      if (this._MediaControl == null) { InitializeControl(); }

      try
      {
        if (this._MediaControl != null)
        {
          // 既にグラフが設定されていた場合、再生を行う
          this._MediaControl.Run();
          return;
        }

        // フィルタグラフマネージャを生成
        this._FilterGraph = new FilgraphManager();

        // 各インターフェースを取得
        this._BasicVideo = (IBasicVideo)this._FilterGraph;
        this._BasicAudio = (IBasicAudio)this._FilterGraph;
        this._MediaControl = (IMediaControl)this._FilterGraph;
        this._MediaEventEx = (IMediaEventEx)this._FilterGraph;
        this._VideoWindow = (IVideoWindow)this._FilterGraph;
        this._MediaPosition = (IMediaPosition)this._FilterGraph;

        // グラフを構築
        this._FilterGraph.RenderFile(this._Source);

        // VideoWindowをメインWindowにアタッチして、子ウィンドウに設定
        this._VideoWindow.Owner = (int)this.Handle;
        this._VideoWindow.WindowStyle = WS_CHILD | WS_CLIPSIBLINGS;

        // 動画のサイズの取得
        var size = CalcDisplaySize();

        // 表示位置のオフセットを取得
        var point = CalcDisplayPoint(size);

        // VideWindowを動画のサイズに設定
        this._VideoWindow.SetWindowPosition(point.X, point.Y, size.X, size.Y);

        // イベント通知を受け取るように所有者ウィンドウを設定
        this._MediaEventEx.SetNotifyWindow((int)this.Handle, WM_GRAPHNOTIFY, 0);

        // グラフを実行
        this._MediaControl.Run();
      }
      catch (Exception ex)
      {
        Console.WriteLine(ex.StackTrace);
        // COMオブジェクトの初期化
        InitializeControl();
      }
    }

    /// <summary>
    /// 引数のサイズからアスペクト比を算出します。
    /// </summary>
    /// <param name="w">横の解像度</param>
    /// <param name="h">縦の解像度</param>
    /// <returns>アスペクト比</returns>
    private Point CalcAspect(int w, int h)
    {
      // 最大公約数を求める
      var gcd = Gcd(w, h);

      var result = new Point();
      result.X = w / gcd;
      result.Y = h / gcd;
      return result;
    }

    /// <summary>
    /// 最大公約数を求めます。
    /// </summary>
    /// <param name="w">横の解像度</param>
    /// <param name="h">縦の解像度</param>
    /// <returns></returns>
    private int Gcd(int w, int h)
    {
      if (w < h)
      {
        return Gcd(h, w);
      }
      while (h != 0)
      {
        var remainder = w % h;
        w = h;
        h = remainder;
      }
      return w;
    }

    /// <summary>
    /// 表示する動画のサイズを算出します。
    /// </summary>
    /// <returns>動画のサイズ</returns>
    private Point CalcDisplaySize()
    {
      // コントロールのサイズ取得
      var dh = this.Height;
      var dw = this.Width;

      // 動画のサイズ取得
      var sh = 0;
      var sw = 0;
      this._BasicVideo.GetVideoSize(out sw, out sh);
      // アスペクト比を求める
      var aspect = CalcAspect(sw, sh);

      // 表示解像度
      var h = dh;
      var w = dw;

      // コントロールと動画の高さを比較し、大きい方を設定する
      // = 高さの解像度 / 高さの比率
      // = 比率*幅の比率

      // = 幅の解像度 / 幅の比率
      // = 比率*高さの比率

      // 高さを求める
      var ratio = ((double)h / (double)aspect.Y);
      var w1 = (int)(ratio * aspect.X);
      // 幅を求める
      ratio = ((double)w / (double)aspect.X);
      var h1 = (int)(ratio * aspect.Y);

      if (h1 < h)
      {
        h = h1;
      }
      if (w1 < w)
      {
        w = w1;
      }

      return new Point(w, h);
    }

    /// <summary>
    /// 動画を表示する位置を算出します。
    /// </summary>
    /// <returns>動画の表示位置</returns>
    private Point CalcDisplayPoint(Point size)
    {
      // コントロールのサイズ取得
      var h = this.Height;
      var w = this.Width;

      var x = 0;
      var y = 0;

      // 動画の幅とコントロールの幅を比較し動画のサイズが小さい場合、オフセット位置を算出する
      if (size.X < w)
      {
        x = (w - size.X) / 2;
      }
      // 動画の高さとコントロールの高さを比較し動画のサイズが小さい場合、オフセット位置を算出する
      if (size.Y < h)
      {
        y = (h - size.Y) / 2;
      }

      return new Point(x, y);
    }

    /// <summary>
    /// 画像を補間処理します。
    /// </summary>
    private void ImageCorrection()
    {
      if (String.IsNullOrEmpty(this.picBackground.ImageLocation)) { return; }

      using (var image = new Bitmap(this.picBackground.ImageLocation))
      {
        this.picBackground.Image = ResizeImage(image, this.picBackground.Width, this.picBackground.Height);
      }
    }

    /// <summary>
    /// 画像リサイズ
    /// </summary>
    /// <param name="image">画像</param>
    /// <param name="width">表示領域の幅</param>
    /// <param name="height">表示領域の高さ</param>
    /// <returns>リサイズを行ったビットマップ画像</returns>
    private Image ResizeImage(Image image, int width, int height)
    {
      // 画像のサイズを算出
      var w = image.Width;
      var h = image.Height;

      var s = System.Math.Min(width / w, height / h);
      var sW = (int)(s * w);
      var sH = (int)(s * h);

      // リサイズした画像を生成する
      var result = new Bitmap(width, height);
      using (var g = Graphics.FromImage(result))
      {
        g.InterpolationMode = InterpolationMode.HighQualityBicubic;
        g.DrawImage(image, (width - sW) / 2, (height - sH) / 2, sW, sH);
        g.Dispose();
      }

      return result;
    }
    #endregion
  }
}

 Windowsのデフォルトだとコーデックがまったくインストールされていないため、WMVやAVIくらいしか再生できませんので、フリーのコーデックパックを使用すると良いでしょう。

【Xamarin】Youtube再生【Android】

XamarinでYoutubeをアプリ内で再生しないといけない場面があり、少し詰まったので自分のメモがてら記事にまとめてみました。
AndroidYoutubeを再生するためにはいくつか手段がありますが、今回はYouTube Android Player APIを使用した再生方法を試しています。

YouTube Android Player APIの準備

XamarinでYouTube Android Player APIを使用する場合はBinding Java Libraryを使用します。
Xamarin公式リファレンス

1.YouTube Android Player API - DownloadよりYouTube Android Player APIのjarファイルをダウンロード

2.BindingsLibraryを作成して、ダウンロードしたjarファイルをJarsフォルダに入れる
(ビルドアクションはEmbeddedJarにする)

f:id:atamo_dev:20161226000054p:plain

f:id:atamo_dev:20161226000158p:plain

3./Transforms/Metadata.xmlを修正

<?xml version="1.0" encoding="UTF-8"?>  
<metadata>  
    <attr path="/api/package[@name='com.google.android.youtube.player']" name="managedName">Google.YouTube.Player</attr>  
    <remove-node path="/api/package[@name='com.google.android.youtube.player']/class[@name='YouTubeThumbnailView']/method[@name='finalize' and count(parameter)=0]" />  
</metadata>

4.一度ビルドした後、実行したいプロジェクトでBindingsLibraryを参照に追加

f:id:atamo_dev:20161226000708p:plain

Google Developers ConsoleでAPIキーを取得

1.GoogleDevelopersConsoleYouTube Data API v3を有効にする

2.認証情報ページよりAPIキーを取得

パーミッションの設定

1.permissionでインターネット接続を許可

コード修正

1.Activityを修正します

using Android.App;
using Android.Widget;
using Android.OS;
using Google.YouTube.Player;

namespace YoutubeSample {
    [Activity(Label = "YoutubeSample", MainLauncher = true, Icon = "@mipmap/icon")]
    public class MainActivity : Activity,IYouTubePlayerOnInitializedListener {
        private static int RECOVERY_DIALOG_REQUEST = 1;
        private readonly string ApiKey = "";
        private readonly string VideoId = "";

        protected override void OnCreate(Bundle savedInstanceState) {
            base.OnCreate(savedInstanceState);
            SetContentView(Resource.Layout.Main);

            YouTubePlayerFragment youTubePlayerFragment = FragmentManager.FindFragmentById<YouTubePlayerFragment>(Resource.Id.youtube_fragment);
            youTubePlayerFragment.Initialize(ApiKey, this);
        }

        public virtual void OnInitializationSuccess(IYouTubePlayerProvider provider, IYouTubePlayer player, bool wasRestored) {
            if (!wasRestored) {
                player.CueVideo(VideoId);
            }
        }

        public void OnInitializationFailure(IYouTubePlayerProvider provider, YouTubeInitializationResult errorReason) {
            if (errorReason.IsUserRecoverableError) {
                errorReason.GetErrorDialog(this, RECOVERY_DIALOG_REQUEST).Show();
            } else {
                Toast.MakeText(this, errorReason.ToString(), ToastLength.Long).Show();
            }
        }
    }
}

2.レイアウトxmlを修正

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <fragment
        android:id="@+id/youtube_fragment"
        android:name="com.google.android.youtube.player.YouTubePlayerFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
</LinearLayout>

実行画面

f:id:atamo_dev:20161226000725p:plain

実行すると以下のような画面になり、youtubeが再生できることが確認できます。(実機推奨)