[an error occurred while processing this directive]
[an error occurred while processing this directive]
(6) 背景を書いたり、影をつけたり豪華にする
前回は新たに全部の写真が並んだ、一覧表示モードを追加しました。
前回 から変わったのは、以下の点です。
- パラメータを html ファイルの <param> タグから読み込めるようにした
- 背景を描画できるようにした。小さいjpgeファイルを敷き詰めるタイプ
- 画面遷移のときに、全体を覆う影を描画するようにした (実物でmode change させてみてください)
Env: 色々な定数を入れるクラス
初期化をIvylet の中で行うことにしたので、初期化が無くなりました。
あと、背景に画像を使うようになったので BACKGROUND_URL が増えました。
class Env {
public static int W, H, BTN_W;
public static int T_MAX;
public static int REFRESH;
public static double WH_RATIO;
public static String URL, BACKGROUND_URL;
}
Ivylet: メインのアプレットのクラス
色んなパラメータを、Java で埋め込むのではなく、htmlの <param> タグから読むようにしました。
これにより、出来たクラスファイル(あるいは jar ファイル) だけ持って行って、適当に設定を書いた html を作ると、
色んな写真集を作れるようになります。
パラメータの読み込みは簡単で、JApplet クラスについてるgetParameter()を使えばOK です。
ただ、そのままでは全部文字列なので、数値のものはなおしてやります。
public class Ivylet extends JApplet {
public void init() {
loadParameter();
String[] imgPaths = extractPaths(Env.URL);
IvyPanel tp = new IvyPanel(imgPaths);
setContentPane(tp);
new Timer(Env.REFRESH, tp).start();
}
/** 各種パラメータのデフォルト値を設定 */
void loadParameter() {
Env.W = getIntParameter("width", 800);
Env.H = getIntParameter("height", 600);
Env.BTN_W = getIntParameter("button_width", 30);
Env.T_MAX = getIntParameter("t_max", 20);
Env.WH_RATIO = getDoubleParameter("wh_ratio", 1.333333);
Env.REFRESH = getIntParameter("refresh", 15);
Env.URL = getParameter("url", "http://funini.com/kei/ivy/narita/");
Env.BACKGROUND_URL = getParameter("background_url",
"http://funini.com/kei/ivy/imgs/back.jpg");
}
/** param タグから String のパラメータを取得。失敗時はデフォルト値を返す */
String getParameter(String key, String defval) {
String s = getParameter(key);
return (s == null) ? defval : s;
}
/** param タグから int のパラメータを取得。失敗時はデフォルト値を返す */
int getIntParameter(String key, int defval) {
try {
String s = getParameter(key);
return (s == null) ? defval : Integer.parseInt(s);
} catch (Exception e){ return defval; }
}
/** param タグから、double のパラメータを取得。失敗時はデフォルト値を返す */
double getDoubleParameter(String key, double defval) {
try {
String s = getParameter(key);
return (s == null) ? defval : Double.parseDouble(s);
} catch (Exception e) { return defval; }
}
/** 指定されたパスから、画像の URL 一覧を取得 */
public String[] extractPaths(String urlStr) {
// urlBase には、URLの / 以前の部分が入る ( http://funini.com/kei/ など)
String urlBase = urlStr.substring(0, urlStr.lastIndexOf("/") + 1);
ArrayList files = new ArrayList();
try {
BufferedReader br = new BufferedReader(new InputStreamReader(
new URL(urlStr).openConnection().getInputStream()));
extractFiles(files, urlBase, br);
} catch (IOException ie) {
ie.printStackTrace();
}
return (String[]) files.toArray(new String[0]);
}
/** br から全体を読み込み、画像の URL 一覧を取得 */
void extractFiles(ArrayList files, String urlBase, BufferedReader br) throws IOException{
Pattern pattern = Pattern.compile("^(.+\\.(jpg|JPG))<.*");
for(;;) {
String line = br.readLine();
if(line == null) break;
String[] targets = line.split(">");
for(int i = 0; i < targets.length; i++) {
Matcher m = pattern.matcher(targets[i]);
if(m.matches()) files.add(urlBase + m.group(1));
}
}
}
}
IvyPanel: メインのパネル
画面遷移のときに画面全体に被せる影と、背景を追加しました。
詳しい説明は、それぞれのクラスの説明をどうぞ。
class IvyPanel extends JPanel implements ActionListener, MouseListener {
Button button; // ボタンです。クリックすると表示モードが変わります
Shadow shadow; // 全体に被せる影です。モードが変わるときに描画されます。
Background back; // 背景です。
IconList iconList; // 各画像の重ね順です。
boolean listmode; // 表示モードです。「一覧表示(true)」と「重ね表示(false)」があります
public IvyPanel(String[] urls) {
/** コンストラクタ 引数は画像が入っているパス */
setBackground(new Color(0x33, 0x33, 0x33)); // 背景色設定
setPreferredSize(new Dimension(Env.W, Env.H)); // サイズ設定
button = new Button(0, Env.H - Env.BTN_W, 210, Env.BTN_W); // ボタンを配置
shadow = new Shadow(Env.W, Env.H); // 影作成
back = new Background(Env.BACKGROUND_URL, Env.W, Env.H);
iconList = new IconList(urls);
listmode = false;
addMouseListener(this); // マウスのイベントを扱えるようにする
}
public synchronized void mousePressed(MouseEvent e) {
/** マウスがクリックされたときのイベント */
Point p = e.getPoint();
if(button.hit(p)) { // ボタンがクリックされた場合
listmode = !listmode; // 一覧表示 と 重ね表示を切り替え
initView(); // 初期表示
return;
}
// ボタン以外がクリックされた時
for(int i = iconList.size() - 1; i >= 0; i--) { // クリックは表示と逆順で調べる
Icon icon = iconList.get(i); // i 番目のアイコンについて
if(icon.hit(p)){ // クリックされている場合
if(listmode)
iconList.listTop(icon); // icon をトップに表示 (一覧表示)
else
iconList.piledTop(icon); // icon をトップに表示 (重ね表示)
return;
}
}
// ボタンもアイコンもクリックされていない場合、初期表示に戻る
initView();
}
void initView() {
/** 初期表示を行う */
if(listmode) iconList.list(); // 一覧表示
else iconList.pile(); // 重ね表示
shadow.animationStart(); // 影を表示
}
/** タイマイベントが発生したら、時間を進める */
public void actionPerformed(ActionEvent ae)
{ addTime(); }
/** 時間を進める (何度も呼ばれると困るから、synchronized にしました) */
synchronized void addTime() {
for(int i = 0; i < iconList.size(); i++) // 全てのアイコンについて
iconList.get(i).addTime(); // 時間を進める
shadow.addTime(); // 影も時間を進める
repaint(); // 再描画
}
/** 描画を行う */
public void paintComponent(Graphics g) {
super.paintComponent(g); // ウィンドウを表示
Graphics2D g2 = (Graphics2D) g; // アンチエイリアスの設定など
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
back.draw(g2); // 背景を表示
button.draw(g2); // ボタンを表示
for(int i = 0; i < iconList.size(); i++)
iconList.get(i).draw(g2); // 各アイコンを表示
shadow.draw(g2); // 影を表示
}
/** MouseListener を実装するために必要なメソッド群 */
public void mouseReleased(MouseEvent e) {}
public void mouseEntered(MouseEvent e) {}
public void mouseExited(MouseEvent e) {}
public void mouseClicked(MouseEvent e) {}
}
IconList: アイコンのリスト、重ね順、位置リストなどを保持するクラス
変化無いので省略します。
IconPosition: アイコンの位置を持つクラス
変化無いので省略します。
PiledPosition: 重ね表示のアイコンの位置を持つクラス
変化無いので省略します。
ListPosition: 一覧表示のアイコン位置を持つクラス
変化ないので省略します。
Rect: パネルに置く色んな部品の基本クラス
変化無いので省略します。
Icon: アイコンクラス
変化ありません。
class Icon extends Rect {
int[] dest, orig; // 移動先と移動元のx, y, w, hを持つ配列
public Icon(String imgPath) {
super(0, 0, 0, 0);
loadImage(imgPath);
col = new Color(0.85f, 0.85f, 0.75f, 0.3f);
dest = new int[] { 0, 0, 0, 0 };
orig = new int[] { 0, 0, 0, 0 };
}
/** アニメーションの目的地を設定 */
public void setDestination(int[] A) {
for(int i = 0; i < 4; i++) {
orig[i] = P[i];
dest[i] = A[i];
}
animationStart(); // 時間変数 t,state を初期化
}
/** 時間を進め、state に応じて、このオブジェクトを移動させる。
* 移動元は orig, 移動先は dest。*/
public void addTime() {
super.addTime();
for(int i = 0; i < 4; i++)
P[i] = (int) (orig[i] * (1 - state) + dest[i] * state);
}
/** アイコンを描画する */
void draw(Graphics2D g) {
// まずは、画像が半分以上画面外にはみ出ている場合は描画しない
if(P[0] + P[2] / 2 < 0 || P[1] + P[3] / 2 < 0
|| Env.W <= P[0] || Env.H <= P[1]) return;
g.setColor(col);
// アイコンの周りの、少し透明な枠を描画する
g.fillRect(P[0], P[1], P[2], P[3]);
// 少し余白をあけて、アイコンを描画する
int marginX = P[2] / 30;
int marginY = P[3] / 30;
g.drawImage(img, P[0] + marginX, P[1] + marginY, P[0] + P[2] - marginX,
P[1] + P[3] - marginY, 0, 0, imgW, imgH, null);
}
}
Button: ボタンクラス
変化ないので省略します。
[New]Shadow: 画面全体を暗くする影
モード変更時や、重ね表示で初期表示に戻るときに、
ちょっと透明にして、濃い茶色の四角を上から描画することで、画面全体が遷移している感を出します。
class Shadow extends Rect {
public Shadow(int w, int h) { super(0, 0, w, h); }
public void draw(Graphics2D g) {
/** 影を全体に描画する */
if(t == Env.T_MAX) return; // 影が終了していれば return
// 透明度を計算する
float alpha = (float)(0.5 - 2 * Math.pow(state - 0.5, 2));
g.setColor(new Color(0.6f, 0.4f, 0.2f, alpha));
// 全体を塗りつぶす
g.fillRect(0, 0, Env.W, Env.H);
}
}
[New] Background: 背景 (指定された画像を並べて表示)
背景を作りました。四角いタイル上の背景(
こんなの) を、Env.W と Env.H だけ並べて表示します。
class Background extends Rect {
/** コンストラクタ。引数: 画像のURL(またはパス)、幅、高さ*/
public Background(String url, int w, int h) {
super(0, 0, w, h);
loadImage(url);
}
/** img にロードされた画像を、指定された幅・高さを満たすよう敷き詰める */
public void draw(Graphics2D g) {
int n = (int) Math.ceil((double) P[2] / imgW);
int m = (int) Math.ceil((double) P[3] / imgH);
for(int i = 0; i < n; i++)
for(int j = 0; j < m; j++)
g.drawImage(img, i * imgW, j * imgH, null);
}
}
ImageLoader: 画像を読み込むためのクラス
前回から変更無いので省略します。
[an error occurred while processing this directive]