Graphics2Dの詳細

こんにちは、ガーディアン!

今日は、Javaでグラフィックスを操作する際のいくつかの側面と微妙な点に再び注意を向けようとします。 前回の記事で、コンポーネントとUIを作成するための利用可能なツールと方法のいくつかを簡単に説明しましたが、これは氷山の一角にすぎません。 そのため、グラフィックスを扱うために特別な注意(および記事)を払いたいのです。 もちろん、Graphics2Dを意味します-Java 3Dは別の大きなトピックです(多分それは将来議論されるでしょうが、今日は議論されないでしょう)。

したがって、前の記事から、コンポーネントの構築の基本をすでに知っているはずです-この知識を広げようとします。

まず、 MVCの観点からコンポーネントを見ると、3つの部分で構成されています。
モデル -コンポーネントの状態に関するデータを格納し、それに基づいて外観が構築されるモデル
表示 -コンポーネントを直接視覚的に表示
コントローラー -コンポーネント(キーボード、マウス、その他の入力デバイスからのイベント)の管理を担当します

実際、すべての標準SwingコンポーネントはMVCパターンを使用して構築されています。 たとえば、JButtonの場合、ButtonModelはボタンの動作と状態(ControllerとModel)、ButtonUIは外部表現(View)を担当します。 その結果、JButtonクラス自体にはほとんど何も残りません。 これは主に、コンポーネントの外部表現(View)の実装を扱い、Graphics2Dを指定した場合、実際にはこれに基づいてインターフェース全体が描画されます。

私はこのトピックに多くの異なる資料があると主張しませんが、それは非常に断片化されており、ネットワークの広大な広がりの周りに散らばっているため、1つの場所にすべてを集めて一貫して述べることは場違いではないようです。


服で会いましょう

誰が言ったとしても、インターフェースは常にアプリケーションの成功に重要な役割を果たし、果たし、果たします。 アプリケーションの対象者に応じて、インターフェースの役割は二次的であるか、その逆です-主要で最も重要です。 たとえば、ゲームやさまざまなエディターアプリケーションの場合、インターフェイスと使いやすさがすべてを決定します(これは、十分頻繁に、または長期間使用するアプリケーションであるためです)。

そのため、本日は、Graphics2Dが提供する標準ツールと、複雑なコンポーネントをレンダリングするためのテクニックとトリックを分析します。

賢明な考えがなければ、何もするのを助ける手段はないことは明らかですが、残念ながら、私は無力です。 おそらくファンタジーですべてが本当に悪いのであれば、おそらくデザイナー/ UXの専門家があなたを助けることができます。 正直なところ、新しいコンポーネントがどのように見えるかを自分で「絞り出す」のは難しい場合があります。 作業コードを直接記述してデバッグするよりもさらに複雑で時間がかかります。

いずれにせよ、ビジネスに取りかかりましょう...

小さな目次

  1. フィギュア
  2. レンダリングヒント
  3. 注ぐ
  4. 図面の配置
  5. ストローク
  6. 正しいクリッピング
  7. アニメーション
  8. いくつかのトリック
  9. WebLookAndFeel
  10. 結論として...
  11. 資源

フィギュア

数字なしではどこにもありません。 コンポーネント、最も単純なものについては、パーツの輪郭を描く必要があります。 手動でピクセル単位で実行するのは楽しい作業ではありません。 特に、レンダリングにスムージングまたはその他の効果を追加する必要がある場合。 手動でこれを行う必要はありません-レンダリングに必要なすべての標準図形(Line2D、Rectangle2D、RoundRectangle2D、Ellipse2D、Arc2Dなど)は既に実装されています-特定の場所に描画するための座標とサイズを指定するだけです

標準のシェイプに加えて、独自のシェイプをすばやく作成できる特定のシェイプ(GeneralPathなど)もあります。

さまざまな特定の形式を持ついくつかの個別のプロジェクトもあります。
http://java-sl.com/shapes.html
http://geosoft.no/graphics/
http://designervista.com/shapes/index.php
http://www.jfree.org/jcommon/

個々の図を説明することは意味がありません- ここまたはここで詳細に読むことができます (両方の説明には使用例もあります)。

ここで、図がどのように描かれ、最終結果にどのような影響があるかについて少し明確にしたいと思います。 特定の形状(形状)があるとします:


レンダリングヒント

アンチエイリアスは、Graphics2Dにバンドルされている標準ツールのセットに含まれています。
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING、RenderingHints.VALUE_ANTIALIAS_ON);
これは、将来描画されるすべての形状の平滑化を可能にするのに十分です。
覚えておくべき主なことは、操作後にアンチエイリアスをオフにすることです。後にレンダリングされるすべてのものにアンチエイリアスも使用したくない場合-たとえば、ボタンの背景レンダリングを実装し、デフォルトで描画されるテキストのスムージングを発生させたくない場合。

このパラメーターの値(RenderingHints.KEY_ANTIALIASING)は、デフォルトの選択がある場合、テキストの平滑化にも影響します。
テキストスムージングの必要性を個別に有効化/無効化することができます。
g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING、RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
このパラメーターがVALUE_TEXT_ANTIALIAS_DEFAULT以外に設定されている場合、値RenderingHints.KEY_ANTIALIASINGは無視されます。

以下は、アンチエイリアスを使用した場合と使用しない場合のレンダリングの違いを示す小さくシンプルな例です。

(例とソースコードはこちらからダウンロードできます

よく見ると、RenderingHintsで利用可能な他の多くの設定にも気付くでしょう。
KEY_ANTIALIASING-形状(およびテキスト)を平滑化するための設定
KEY_RENDERING-品質/レンダリング速度の設定
KEY_DITHERING-制限されたパレットでの色の混合
KEY_TEXT_ANTIALIASING-テキストのアンチエイリアス設定
KEY_TEXT_LCD_CONTRAST-特殊なテキストスムージングを使用してレンダリングするときのテキストコントラスト(100〜250)
KEY_FRACTIONALMETRICS-テキスト文字のレンダリングの「精度」の設定
KEY_INTERPOLATION-レンダリング中のイメージピクセルの変更を担当する設定(たとえば、イメージが回転する場合)
KEY_ALPHA_INTERPOLATION-アルファ値の品質/処理速度の設定
KEY_COLOR_RENDERING-品質/カラー処理速度の設定
KEY_STROKE_CONTROL-シェイプのジオメトリを変更して最終ビューを改善する機能を設定する

通常、これらの設定のほとんどはデフォルト状態のままです。 ただし、特定のケースでは非常に役立ちます。
たとえば、KEY_INTERPOLATIONをVALUE_INTERPOLATION_BILINEARに設定すると、変更時の画質の低下(回転/圧縮/シフトなど)を回避したり、テキストレンダリングコードに影響を与えずにKEY_TEXT_LCD_CONTRASTを変更して背景のテキストのコントラストを改善できます。

いずれの場合も、これらの設定を慎重に使用し、レンダリングメソッドの範囲を超えないようにしてください。たとえば、JTextFieldに含まれる同じアンチエイリアシングは、テキストの平滑化とその変更(およびおそらくシフト)につながるためです。

注ぐ

Paintクラスのいくつかのアクセス可能な子孫があり、レンダリングされた図形をさまざまな方法でペイント/塗りつぶすことができます。


わかりやすくするために、3つの異なるグラデーションを使用してコンポーネントの3つの等しい部分を埋める小さな例を示します。

(例とソースコードはこちらからダウンロードできます

ちなみに、色を指定する際の塗りつぶし/レンダリングでは、透明度(アルファ)も指定できることを誰もが知っているとは限りません。
たとえば、新しい色(255、255、255、128)-50%の白色まで透明です。 この場合のアルファは128です。0から255まで変化できます。0は完全に透明な色で、255は完全に不透明です(デフォルトで使用)。

図面の配置

それで、私たちは体系的にもっと複雑なことに移行しています...
Alignment(言い換えれば、Composite)を使用すると、キャンバスに既にピクセルが配置されている新しい描画図形/画像のさまざまな「モード」の配置を指定できます。

ここでは、さまざまなアライメントの「モード」の最も成功したイラストと、描かれた人物に対する実際の影響の例を見つけることができました 。 各「モード」の詳細な説明もあります。

実際には、個人的には、AlphaComposite.SRC_OVERオプションを使用し、既に描画されているものの上に特定の透明度でさらに要素を描画するために透明度を指定することがほとんどです。 また、画像を操作するときにいくつかのモードを適用する非常に興味深い例もありますが、それについては後で詳しく説明します。

コンポジットに加えて、標準のシェイプを使用して独自のシェイプを作成し、さまざまな幾何学的操作と組み合わせることもできます。 このトピックに関する小さな例:

(例とソースコードはこちらからダウンロードできます

この図を作成するのに必要なのは2行だけです。
Area area = new Area ( new Ellipse2D.Double ( 0, 0, 75, 75 ) );
area.add ( new Area ( new Ellipse2D.Double ( 0, 50, 75, 75 ) ) );

最も興味深いのは、新しい図形の境界線には、楕円の境界線(楕円の反対側にある境界線)の内部部分が含まれていないことです。

一般的に、Areaはさまざまな用途に使用できます。既存の図形に新しい図形を追加できるだけでなく、交差領域を作成したり、一部の図形を他の図形から除外したり、そこから図形の共通の境界(境界)を学習することもできます。 また、その助けを借りて、他の利用可能な単純な形状から複雑な形状をすばやく簡単に作成できます。

頻繁に使用する特定の形状が必要な場合、おそらく最善の方法は、Shapeクラスの継承者を作成し、必要な形状をそのクラスに実装することです。 将来的には、これにより費やされる時間とコード自体のサイズの両方が削減されます。

ストローク

...またはTheShockが提案した「ストローク」。

実際、Strokeは、グラフィックスのdrawメソッドの呼び出しによって描画される境界線のスタイルを設定する機能を提供します。
標準JDKには、Strokeインターフェースの実装が1つしかありません-BasicStroke。 線の幅、線が角でどのように結合されるか、端でどのように見えるかを設定し、破線も作成できます。

コードでストロークを設定するには、次の手順を実行します。
g2d.setStroke ( new BasicStroke ( 2f ) );
この例では、後続のすべてのボーダーボードを強制的に2ピクセル幅で描画します。
ちなみに、幅やその他のパラメータはフロートで設定できることを恐れないでください(ピクセルは整数でなければなりません)-非整数の数値は、レンダリング時に「スミア」線/アウトラインのみを作成し、場合によっては便利になることもあります。

BasicStrokeの可能性に関する詳細情報は、たとえばここにあります

JDKにはStrokeの実装が1つしか含まれていませんが、このツールの実際の機能を説明する他のプロジェクトと例がありますが、それについては後で詳しく説明します。

Java2Dには影の標準的な実装はありませんが、影の「効果」を達成する方法はかなりあります。それらについてはこの章で説明します。
最も簡単なオプションから始めましょう...

元の形状のわずかなシフト/変更によって得られた影

2番目の画像-このタイプの影の細くてきれいなバージョン。
(例とソースコードはこちらからダウンロードできます

この方法で得られた影は、元の形状の端から1〜3ピクセルを超えない範囲でのみ適用できます。それ以外の場合、不自然に見え始めます。 また、この方法にはかなり面倒なコードが必要です。個々の図については、影を繰り返しチェックして選択する必要があります。

反復的な影
変更された形状を繰り返しレンダリングすることによって得られる影。 さらに、後続の各反復で、(オプションの1つとして)図形のサイズが大きくなり、透明度が低下します(または色が変わります)。

(例とソースコードはこちらからダウンロードできます

このオプションは、シャドウのサイズを制限しないという点で優れていますが、同時により面倒で修正がさらに困難です。 また、レンダリング速度の点ですべてのオプションの中で最も最適ではありません。

グラデーションシャドウ
このオプションは、Figureのエッジに沿ったグラデーション塗りつぶしの使用に基づいています。
実際、塗りつぶしが必要な長方形の場合、エッジの周りに8つの部分があります。

4つの場合-LinearGradientPaint、他の4つの場合-RadialGradientPaint。 結果は、このようなきちんとした影になります。

(例とソースコードはこちらからダウンロードできます

また、グラデーションの位置を変更して、次のような他の興味深いオプションを取得することもできます。

(例とソースコードはこちらからダウンロードできます

このオプションの利点は、シャドウレンダリングの速度と品質です。 ただし、例からわかるように、コードのサイズは再び低下します。

レンダリング時にストロークを変更して得られる影
より正確には、可変の色/透明度とストロークを使用して、図をサイクルで数回描画します。これにより、一種の影を作成できます。

(例とソースコードはこちらからダウンロードできます

図形の形状は影のレンダリングに絶対に影響しないため、この手法は最もトリッキーな図形でも使用できます。

(例とソースコードはこちらからダウンロードできます

また、シャドウレンダリング自体を別の独立したメソッドに簡単に移動できます。
private void drawShade ( Graphics2D g2d, RoundRectangle2D rr, Color shadeColor, int width )
{
Composite comp = g2d.getComposite ();
Stroke old = g2d.getStroke ();
width = width * 2;
for ( int i = width; i >= 2; i -= 2 )
{
float opacity = ( float ) ( width - i ) / ( width - 1 );
g2d.setColor ( shadeColor );
g2d.setComposite ( AlphaComposite.getInstance ( AlphaComposite.SRC_OVER, opacity ) );
g2d.setStroke ( new BasicStroke ( i ) );
g2d.draw ( rr );
}
g2d.setStroke ( old );
g2d.setComposite ( comp );
}

画像の影
画像の場合、以前の方法で影をレンダリングするための図形を取得することはできません。それが長方形であるだけでなく、たとえば円形または不定形の場合でもです。 この場合に影を作成するには、もう少しアプローチします-AlphaCompositeを使用して、元の画像の単色コピーを作成し、それを影として使用します。

(例とソースコードはこちらからダウンロードできます

もちろん、場合によっては、このような影で十分ですが、それでも、影自体の近くでより滑らかな/グラデーションのエッジを取得したいと思います。 この問題を解決するには、フィルタリングが役立ちます。 より正確には、結果の画像の「影」に特別なフィルターを使用して、よりリアルな外観にします。

(例とソースコードはこちらからダウンロードできます

率直に言って、この例では、 ここで指定したフィルターの完成版を使用しまし 。 ただし、追加の資金がなくても、最初の例で取得した影をすばやく簡単に「ぼかし」、画像の下に配置できます。

ちなみに、この影は、最初に別の画像に適用すると、フィルターが機能することに基づいて、図に使用することもできます。 これは、たとえば、アプリケーションの実行中に図が動的に変化しない場合に適用できます。一度「キャプチャ」して、レンダリング時に結果のイメージを使用するだけです。 ただし、個人的には、リソースコストの観点からこのオプションがあまり好きではないため、リストから削除しました。

正しいクリッピング

グラフィックスを操作し、「正しい」コンポーネントを作成するときに必要な別の重要な側面、クリップの操作、またはレンダリング中の不要な部分の切断について個別に説明したいと思います。

このツールを使用するには、「クリッピング」が発生するフォームを指定するだけで十分です。
g.setClip ( x, y, width, height );
g.setClip ( new Rectangle ( x, y, width, height ) );
g.setClip ( new Rectangle2D.Double ( x, y, width, height ) );
これらの3つの方法はすべて、同じ長方形のクリッピング領域の確立につながります。

このツールが役立つ場合が多くあります。
まず、コンポーネントをレンダリングするとき、特定のクリッピングフォームが常に事前定義されています(通常、これはコンポーネントの境界です)-コンポーネントが境界から「クロールアウト」することを許可しません。 特定のクリッピング領域を設定するときは、これを考慮する必要があります。 簡単に言うと、次のようにできます。
Shape oldClip = g.getClip ();
Shape newClip = new Rectangle ( x, y, width, height );
Area clip = new Area ( oldClip );
clip.intersect ( new Area ( newClip ) );
g.setClip ( clip );
実際、既存のクリッピング領域を新しい領域と組み合わせます。 この方法では、コンポーネントの境界の形で制限を失うことはありませんが、必要な新しい制限も追加します。

影の作成に関する章に戻って、ポイント4をより正確に言えば、影の一部を切り取るだけで改善できます。
public static void drawShade ( Graphics2D g2d, Shape shape, Color shadeColor, int width,
Shape clip, boolean round )
{
Shape oldClip = g2d.getClip ();
if ( clip != null )
{
Area finalClip = new Area ( clip );
finalClip.intersect ( new Area ( oldClip ) );
g2d.setClip ( finalClip );
}

Composite comp = g2d.getComposite ();
float currentComposite = 1f;
if ( comp instanceof AlphaComposite )
{
currentComposite = ( ( AlphaComposite ) comp ).getAlpha ();
}

Stroke old = g2d.getStroke ();
width = width * 2;
for ( int i = width; i >= 2; i -= 2 )
{
float opacity = ( float ) ( width - i ) / ( width - 1 );
g2d.setColor ( shadeColor );
g2d.setComposite ( AlphaComposite
.getInstance ( AlphaComposite.SRC_OVER, opacity * currentComposite ) );
g2d.setStroke (
new BasicStroke ( i, round ? BasicStroke.CAP_ROUND : BasicStroke.CAP_BUTT,
BasicStroke.JOIN_ROUND ) );
g2d.draw ( shape );
}
g2d.setStroke ( old );
g2d.setComposite ( comp );

if ( clip != null )
{
g2d.setClip ( oldClip );
}
}

したがって、目的のクリッピング領域をこのメソッドに追加で転送することが可能になります。メソッド自体が残りを行います。

実行されたいくつかのテストが示すように、描画不可能な部分(たとえば、画面から消える部分)を切り取っても、作業速度が大幅に向上することはありません。 ただし、「何を、どこで、どのように描画するか」などのすべての計算とレンダリング自体は、クリップが1つの「使用可能な」ピクセルに設定されている場合でも機能します。 そのため、このような場合には「手動」最適化がはるかに役立ちます。

ほとんどの場合、クリップは、複雑なパーツをレンダリングし、隣接するコンポーネントが互いに重ならないように境界を正しく制限するために作成されました。

アニメーション

それで、得られた知識の一部を組み合わせて、もっと面白いことをする時が来ました。

アニメーション自体は非常に理解しやすく、レンダリングされたオブジェクトの経時的な変化のみを表します。 ただし、実際にはさらに多くの質問と問題があります。

アニメーションの種類によっては、「イベントの開発」を担当し、変更を表示する多くの追加コードが必要になる場合があります。 再描画時に最適化を忘れないことも重要です。つまり、変更が発生したアニメーションコンポーネントの領域のみを再描画することをお勧めします。 これを行うには、repaint(新しいRectangle(x、y、width、height))メソッドを呼び出すだけです。

アニメーションの実装の小さな例を考えてみましょう-JLabelのテキストを介してグレアの効果を作成します。 これを行うには、まず、実装に必要なものを明確に理解するために、その外観を決定する必要があります。

「ハイライト」の基礎として、グラデーションで満たされた円(中央の明るい灰色から端のフォントの色(黒)まで)を見てみましょう。 また、この円をコンポーネントの最初から最後まで移動させるための別のタイマーも必要です。

したがって、次のようなものがコンポーネントをレンダリングします。
private boolean animating = false ;
private int animationX = 0;
private int animationLength = 140;

private float [] fractions = { 0f, 1f };
private Color[] colors = new Color[]{ new Color ( 200, 200, 200 ), new Color ( 0, 0, 0 ) };

protected void paintComponent ( Graphics g )
{
// ,
BufferedImage bi =
new BufferedImage ( getWidth (), getHeight (), BufferedImage.TYPE_INT_ARGB );
Graphics2D g2d = bi.createGraphics ();
g2d.setFont ( g.getFont () );

//
super.paintComponent ( g2d );

//
if ( animating )
{
g2d.setRenderingHint ( RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON );

g2d.setComposite ( AlphaComposite.getInstance ( AlphaComposite.SRC_IN ) );
g2d.setPaint ( new RadialGradientPaint ( animationX - animationLength / 2,
getHeight () / 2, animationLength / 2, fractions, colors ) );
g2d.fillRect ( animationX - animationLength, 0, animationLength, getHeight () );
}

//
g2d.dispose ();
g.drawImage ( bi, 0, 0, null );
}
ハイライトをレンダリングするときにCompositeをインストールするのと同様に、テキストが描画される別の画像を作成する際に主要なポイントは隠されます。

画像は、テキストを含むピクセルのみが占有されるように必要です。そうでない場合、Graphics AlphaComposite.SRC_INメソッドの標準レンダリングでは、塗りつぶされた長方形全体を指定されたグラデーションで塗りつぶします。チャート上のテキストに加えて、描画パネルがすでに存在するためです。 (パネル)背景。

そのため、たとえばカーソルがJLabel領域に入ったときに機能するタイマーを実装することは今も残っています。
private static class AnimatedLabel extends JLabel
{
public AnimatedLabel ( String text )
{
super ( text );
setupSettings ();
}

private void setupSettings ()
{
//
setOpaque ( false );
//
setFont ( getFont ().deriveFont ( Font .BOLD ).deriveFont ( 30f ) );

// ,
addMouseListener ( new MouseAdapter()
{
public void mouseEntered ( MouseEvent e )
{
startAnimation ();
}
} );
}

private Timer animator = null ;
private boolean animating = false ;
private int animationX = 0;
private int animationLength = 140;

private float [] fractions = { 0f, 1f };
private Color[] colors = new Color[]{ new Color ( 200, 200, 200 ), new Color ( 0, 0, 0 ) };

private void startAnimation ()
{
// ,
if ( animator != null && animator.isRunning () )
{
return ;
}

//
animating = true ;
animationX = 0;
animator = new Timer ( 1000 / 48, new ActionListener()
{
public void actionPerformed ( ActionEvent e )
{
//
if ( animationX < getWidth () + animationLength )
{
animationX += 10;
AnimatedButton. this .repaint ();
}
else
{
animator.stop ();
}
}
} );
animator.start ();
}

protected void paintComponent ( Graphics g )
{
//
}
}
このコードでは、(コメントに記載されているものに加えて)何かを説明する必要があるとは思いません。

その結果、 このような面白い効果が得られます。

もちろん、この例は氷山の一角にすぎません。 想像力があれば、多くの興味深い静的なものやアニメーション化されたものを作成できます。

いくつかのトリック

必要なすべてを実行するための標準ツールを知っているだけでは不十分な場合があります。 さまざまな「トリッキーな」ものを発明しなければなりません。 いくつかの別々の例で、ネットで見つけられるものと「発明された自転車」を皆さんと共有したいと思います。 それでは、ビジネスに取り掛かりましょう...

エッジスムージング
特定の形状に従って画像を切り抜く必要があると仮定しますが、レンダリング時のクリップなどの標準的な手段は嘆かわしい結果につながります。 この場合、AlphaCompositeを使用する必要があります。
ImageIcon icon = new ImageIcon ( iconPath );

BufferedImage roundedImage = new BufferedImage ( icon.getIconWidth (), icon.getIconHeight (),
BufferedImage.TYPE_INT_ARGB );
Graphics2D g2d = roundedImage.createGraphics ();
g2d.setRenderingHint ( RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON );
g2d.setPaint ( Color.WHITE );
g2d.fillRoundRect ( 0, 0, icon.getIconWidth (), icon.getIconHeight (), 10, 10 );
g2d.setComposite ( AlphaComposite.getInstance ( AlphaComposite.SRC_IN ) );
g2d.drawImage ( icon.getImage (), 0, 0, null );
g2d.dispose ();

ImageIcon roundedIcon = new ImageIcon ( roundedImage );
したがって、まず角を滑らかにした角丸長方形を描画し、それをステンシルとして使用して、画像を上に重ねます。

カスタムストロークの使用
ずっと前に、私は自分のStrokeクラスの作成に関する非常に興味深い記事を見つけました。
場合によっては、同様のストロークでレンダリングを大幅に促進できます。

ぼかし/シャドウフィルターの使用
同じリソースで、blur'uに関する非常に興味深い記事を見つけることもできます。
画像を扱う人には便利かもしれません。

GlyphVectorを使用する
グラフィックを操作する際の「ストレスの多い」瞬間の1つは、特にこのテキストが変更される可能性がある場合に、テキストのレンダリングです。 テキストを正しく配置するには、そのサイズを計算し、それに基づいて描画する必要があります。
このような計算には2つのツールがあります。
1. FontMetrics
Graphics2Dインスタンス(g2d.getFontMetrics())から直接取得できます。
インデントのさまざまなサイズとインストールされているフォントの高さを定義できます。
2. GlyphVector
このオプションは、特定のテキストのサイズを正確に知ることができるため、Y座標を中心にテキストを配置する必要がある場合に便利です。
FontMetrics fm = g2d.getFontMetrics ();
GlyphVector gv = g2d.getFont ().createGlyphVector ( fm.getFontRenderContext (), "Text" );
Rectangle visualBounds = gv.getVisualBounds ().getBounds ();
また、GlyphVectorから多くの有用な情報、たとえばテキストの外側の境界線(直接その形状)を取得できます。
Shape textOutline = gv.getOutline ();

独自のツールチップマネージャーを作成する
Swingには見苦しいものがたくさんありますが、コンポーネントのUIを変更したり、レイアウトを操作したりすることで簡単にスムーズにすることができます。 場合によっては、物事はそれほど単純ではありません。

すべてのJコンポーネントに実装されたツールチップのシステムは、そのような場合にのみ適用されます。 表示されるすべてのツールチップは、個別の厳密な長方形のポップアップ(ツールチップがウィンドウ内に収まるかどうかに応じて軽量または重量)に表示されるため、この領域と形状によって制限されます。 そして、これはきちんとしたWebインターフェイスと丸みを帯びた形状の時代です!

もちろん、標準のTooltipManagerを壊し、ウィンドウを作成する場所を見つけて、新しい機能を使用してウィンドウに必要な形状を設定できますが、第一に、すべてのOSでまだ機能しない、第二に、非常に良い最適なオプションではありません。

そのため、すべての標準ウィンドウに存在するGlassPaneをツールチップ表示領域として(そして後ですべての種類の内部ウィンドウで)使用するというアイデアを思いつきました。

アイデア自体はいくつかの部分で構成されています。
1.別のマネージャー( TooltipManager.java )。必要に応じて、ツールチップが開いて記憶する特定のウィンドウのGlassPaneを作成します。 ツールチップの追加作成は、GlassPaneで直接行われます。
2. GlassPane( TooltipGlassPane.java )は、イベントをスキップして適切なタイミングでツールチップを表示する透明なパネルです
3.ツールチップ自体( CustomTooltip.java )は、GlassPaneの場所に応じて、快適なデザインでコンテンツを表示する標準のJコンポーネントです。

その結果、表示されるツールタイプは次のようになります。


このアイデアの完全な実装は、以下で説明するライブラリで見ることができます。 非常に大量のコードがあるため、引用しませんでした。 アイデアを実装するクラスの名前は上記に記載されており、プロジェクト内のクラスの名前に対応しています。

編集可能なリスト
場合によっては独自の「自転車」を追加することに加えて、既存のコンポーネントの機能を補完する標準ツールを使用するのは非常に簡単でエレガントです。

誰もがJlistコンポーネントを知っていると思いますが、最初は編集を提供していませんでした。 したがって、今、この欠点を修正する方法を示します。

まず、エディター自体が実装するインターフェースを作成する必要があります。
public interface ListEditor
{
public void installEditor ( JList list, Runnable startEdit );

public boolean isCellEditable ( JList list, int index, Object value );

public JComponent createEditor ( JList list, int index, Object value );

public Rectangle getEditorBounds ( JList list, int index, Object value , Rectangle cellBounds );

public void setupEditorActions ( JList list, Object value , Runnable cancelEdit,
Runnable finishEdit );

public Object getEditorValue ( JList list, int index, Object oldValue );

public boolean updateModelValue ( JList list, int index, Object value , boolean updateSelection );

public void editStarted ( JList list, int index );

public void editFinished ( JList list, int index, Object oldValue, Object newValue );

public void editCancelled ( JList list, int index );
}
このインターフェイスの相続人は、リストにエディターを作成して表示するために必要なすべてを提供します。 これらの関数の完全なセットを毎回継承して定義するのは非常にコストがかかります。さまざまなエディターに多かれ少なかれ共通の部分を実装する抽象クラスを作成しましょう。
public abstract class AbstractListEditor implements ListEditor
{
protected int editedCell = -1;

public void installEditor ( final JList list, final Runnable startEdit )
{
// ,
list.addMouseListener ( new MouseAdapter()
{
public void mouseClicked ( MouseEvent e )
{
if ( e.getClickCount () == 2 && SwingUtilities.isLeftMouseButton ( e ) )
{
startEdit.run ();
}
}
} );
list.addKeyListener ( new KeyAdapter()
{
public void keyReleased ( KeyEvent e )
{
if ( e.getKeyCode () == KeyEvent.VK_F2 )
{
startEdit.run ();
}
}
} );
}

public boolean isCellEditable ( JList list, int index, Object value )
{
return true ;
}

public Rectangle getEditorBounds ( JList list, int index, Object value , Rectangle cellBounds )
{
//
return new Rectangle ( 0, 0, cellBounds.width, cellBounds.height + 1 );
}

public boolean updateModelValue ( JList list, int index, Object value , boolean updateSelection )
{
//
ListModel model = list.getModel ();
if ( model instanceof DefaultListModel )
{
( ( DefaultListModel ) model ).setElementAt ( value , index );
list.repaint ();
return true ;
}
else if ( model instanceof AbstractListModel )
{
final Object[] values = new Object[ model.getSize () ];
for ( int i = 0; i < model.getSize (); i++ )
{
if ( editedCell != i )
{
values[ i ] = model.getElementAt ( i );
}
else
{
values[ i ] = value ;
}
}
list.setModel ( new AbstractListModel()
{
public int getSize ()
{
return values.length;
}

public Object getElementAt ( int index )
{
return values[ index ];
}
} );
return true ;
}
else
{
return false ;
}
}

public void editStarted ( JList list, int index )
{
//
editedCell = index;
}

public void editFinished ( JList list, int index, Object oldValue, Object newValue )
{
//
editedCell = -1;
}

public void editCancelled ( JList list, int index )
{
//
editedCell = -1;
}

public boolean isEditing ()
{
//
return editedCell != -1;
}
}
これで、この抽象クラスに基づいて、たとえばリスト用のテキストエディタなど、 WebStringListEditor.javaのような実装が非常に簡単になります。

最後の瞬間が残ります-リストにエディターをインストールする方法。 それを別のクラスに入れて、便宜上静的にします。
public class ListUtils
{
public static void installEditor ( final JList list, final ListEditor listEditor )
{
// ,
final Runnable startEdit = new Runnable()
{
public void run ()
{
//
final int index = list.getSelectedIndex ();
if ( list.getSelectedIndices ().length != 1 || index == -1 )
{
return ;
}

//
final Object value = list.getModel ().getElementAt ( index );
if ( !listEditor.isCellEditable ( list, index, value ) )
{
return ;
}

//
final JComponent editor = listEditor.createEditor ( list, index, value );

//
editor.setBounds ( computeCellEditorBounds ( index, value , list, listEditor ) );
list.addComponentListener ( new ComponentAdapter()
{
public void componentResized ( ComponentEvent e )
{
checkEditorBounds ();
}

private void checkEditorBounds ()
{
Rectangle newBounds =
computeCellEditorBounds ( index, value , list, listEditor );
if ( newBounds != null && !newBounds.equals ( editor.getBounds () ) )
{
editor.setBounds ( newBounds );
list.revalidate ();
list.repaint ();
}
}
} );

//
list.add ( editor );
list.revalidate ();
list.repaint ();

//
if ( editor.isFocusable () )
{
editor.requestFocus ();
editor.requestFocusInWindow ();
}

//
final Runnable cancelEdit = new Runnable()
{
public void run ()
{
list.remove ( editor );
list.revalidate ();
list.repaint ();

listEditor.editCancelled ( list, index );
}
};
final Runnable finishEdit = new Runnable()
{
public void run ()
{
Object newValue = listEditor.getEditorValue ( list, index, value );
boolean changed =
listEditor.updateModelValue ( list, index, newValue, true );

list.remove ( editor );
list.revalidate ();
list.repaint ();

if ( changed )
{
listEditor.editFinished ( list, index, value , newValue );
}
else
{
listEditor.editCancelled ( list, index );
}
}
};
listEditor.setupEditorActions ( list, value , cancelEdit, finishEdit );

//
listEditor.editStarted ( list, index );
}
};
listEditor.installEditor ( list, startEdit );
}

private static Rectangle computeCellEditorBounds ( int index, Object value , JList list,
ListEditor listEditor )
{
//
Rectangle cellBounds = list.getCellBounds ( index, index );
if ( cellBounds != null )
{
Rectangle editorBounds = listEditor.getEditorBounds ( list, index, value , cellBounds );
return new Rectangle ( cellBounds.x + editorBounds.x, cellBounds.y + editorBounds.y,
editorBounds.width, editorBounds.height );
}
else
{
return null ;
}
}
}

以上で、1行のコードで使用可能なリストにエディターをインストールできます。


主なことは、モデルで非String'amiを使用する場合、エディターへの値の設定/取得方法を変更することを忘れないことです。 これを行うには、 WebStringListEditor.javaの 2つのメソッド(もちろん、必要なエディターの複雑さに応じて)-createEditorおよびgetEditorValueをオーバーライドするだけで十分です。

Webのルックアンドフィール

(特に最近)Swingとグラフィックスの作業に多くの時間を費やしたため、コードのさまざまな場所で必要になることが多い、別のUIライブラリ、高度なコンポーネント、ユーティリティを作成するというアイデアを思いつきました。 そして、このアイデアは少しずつ、独立したライブラリ-WebLookAndFeelの形で実現され始めました。

基礎として、私はこの記事ですでに書いたさまざまなテクニックと開発を取りました。また、他のいくつかのテクニックや開発についても後で詳しく説明します。

ところで、私は私たちの商用製品の第2バージョンの開発を鼻に持っていることを考えると、このアイデアは、そのような機能と能力の離調した必要性によって再び拍車をかけられました。

実際、このライブラリには、この記事で説明したテクニックのほとんどが含まれているだけでなく、グラフィックスの操作に関する他の多くの興味深い有用なものが含まれています。

また、いくつかの技術的な「利点」と機能もあります。

別のサイトで詳細を読むことができます。

多くの人にとって、このライブラリは一種の「自転車」のように見えるかもしれず、誰かがそのような機能が他の有名なライブラリにすでに存在していると主張するかもしれません...
これに私は2つのことを言うことができます:
最初に 、他のライブラリは、特定のUI(多くの場合、松葉杖を使用)のスタイリングの可能性を備えて、せいぜいコンポーネントを提供します。 同じライブラリには、WebLaFの一般的な外観のために既に様式化された追加コンポーネントのセットが含まれています。
第二に -私が追加したものは何もありませんし、ライブラリに追加するだけです。ネットワークの広大さの中で、恐ろしいJColorChooserを置き換える可能性のあるColorChooserの賢明な実装は1つも見つかりませんでした。他にJFileChooser実装はまったくありません。もちろんSWTがありますが、正直に言うと、他の問題、困難、制限があります。このオプションに深く入り込んで捨てないことをお勧めします-すべて同じように、Swingについて話します。

だから、私は完成部品の小さなデモでライブラリクラスに追加したインターフェースと機能「感じる」ことができるように:

(ソースコードの例をダウンロードすることができ、ここで)、

完全なソースコードとライブラリの配布することはで入手できます。
http://weblookandfeel.com/download/

現時点では、ライブラリはまだ完全ではなく、LookAndFeelにマイナーな欠陥があります。コードに「検出されない」場所があり、一部の機能と外観は非常に「論争の的」であり、最高の面。

上記のすべてに関連して、トピックに関するコメント、提案、建設的な批判を聞いてうれしいです:)

結論として...

Javaのグラフィックスに関するあなたの知識がわずかに構造化され、実際により具体的で適用可能になったことを願っています。

上記のWebLookAndFeelライブラリのソースコードが、グラフィックを使った作業の習得に役立つことを願っています。この記事で取り上げた以外にも、さまざまな興味深いもの(たとえば、各標準SwingコンポーネントのUIクラスの実装、LookAndFeelの組織、グローバルマネージャーなど)がありますので、それらを勉強することを強くお勧めします(時間と希望がある場合) 、もちろん)。

また、記事の公開が十分に長い間中断されたことをおpoびします-残念ながら、資料の収集は予想以上に長くかかりました。WebLaFライブラリを多少なりとも使いやすくしたことは言うまでもありません。

次の記事は、おそらく独自のLookAndFeel(もちろん例と写真付き)の作成と、個々のJコンポーネントのUIのいくつかの機能に専念します。

資源

トピックに関するさまざまなサードパーティのリソース:(
記事にリストされているものを含む)

MVC
http://lib.juga.ru/article/articleview/163/1/68/

Shapes
http://java.sun.com/developer/technicalArticles/GUI/ java2d / java2dpart1.html
http://www.datadisk.co.uk/html_docs/java/graphics_java2d.htm

Extended Shapes
http://java-sl.com/shapes.html
http://geosoft.no/graphics/
http ://designervista.com/shapes/index.php
http://www.jfree.org/jcommon/

Composite
http://download.oracle.com/javase/tutorial/2d/advanced/compositing.html

BasicStroke
http:/ /www.projava.net/Glava9/Index13.htm

拡張ストローク
http://www.jhlabs.com/java/java2d/strokes/

ぼかし
http://www.jhlabs.com/ip/blurring.html

WebLookAndFeelで使用されるライブラリは次のとおりです。java

-image-scaling
http://code.google.com/p/java-image-scaling/

TableLayout
http:// java。 net / projects / tablelayout

データのヒント
残念ながら、現在このライブラリで利用できるリソースはありません。Jericho

HTML Parser
http://jericho.htmlparser.net/docs/index.html

およびアイコンセット:

Fugueアイコン
http://code.google。 com / p / fugue-icons-src /

Fatcowアイコン
http://www.fatcow.com/free-icons

特別な感謝...


プロジェクトのソースコードの蛍光ペン:読み取り可能なコードのハイライトのための
http://virtser.net/blog/post/source-code-highlighter.aspx

ホスティング画像画像保存用:
http://hostingkartinok.com/

UPD1:少しの微調整とリンクイラスト
Upd2:さまざまな不正確さおよび発生した問題の修正を含む更新されたライブラリー配布
Upd3:記事内の訂正された不正確さおよび曲がった画像

Source: https://habr.com/ru/post/J127518/


All Articles