20200207のJavaに関する記事は6件です。

【Java】じゃんけんの手の出し方 (paizaランク A 相当)

paizaって?

paiza公式サイト
なんかプログラムを書いて、それにランク付けて評価してくれるところ
求人中の企業に対して自分のプログラミング能力をアピールできる
(実務的とは思えないがちゃんと解ければ最低限の能力があると見れるだろう)

今回解く問題

問題:じゃんけんの手の出し方
・総勝負数N
・総指数M
・相手のハンド列S
の3つが入力として与えられ、総勝負数および総指数をきっちり使った時の相手ハンドに対する最大勝利数を割り出す問題
細かくはリンク先

解答コードと結果

import java.util.ArrayList;
import java.util.Scanner;

/**
 * じゃんけんの手の出し方 (paizaランク A 相当)
 */
public class AS001{

    public static void main(String[] args) {

        //入力
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt();
        int m = sc.nextInt();
        char[] handArray = sc.next().toCharArray();


        //相手ハンド分析
        int[] handCount = new int[3]; //{G, C, P}
        for(char hand : handArray) {
            switch(hand) {
            case 'G':
                handCount[0]++;
                break;
            case 'C':
                handCount[1]++;
                break;
            case 'P':
                handCount[2]++;
                break;
            }//switch
        }//for


        //ハンドパターン列挙
        ArrayList<Integer[]> patternList = new ArrayList<>(100);
        for(int i = 0; i <= n; i++) {
            for(int j = 0; j <= n; j++) {
                int yubi = i * 2 + j * 5; //指数
                int remain = n - i - j; //残勝負数

                if(yubi == m && remain >= 0) {
                    //総勝負数内に指を使い切る組み合わせ
                    Integer[] pattern = {remain, i, j}; //{g, c, p}
                    patternList.add(pattern);
                }else if(yubi > m || remain < 0) {
                    //勝負数を使い切るか、指数がオーバーしたら次のループ
                    break;
                }//if
            }//for
        }//for


        //最大勝利数のパターンを計算
        int maxWin = 0;
        for(Integer[] pattern : patternList) {
            int win = 0;
            win += Math.min(handCount[0], pattern[2]); //相手がグー、自分がパー
            win += Math.min(handCount[2], pattern[1]); //相手がパー、自分がチョキ
            win += Math.min(handCount[1], pattern[0]); //相手がチョキ、自分がグー
            if(win > maxWin) maxWin = win;
        }//for


        //出力
        System.out.println(maxWin);
    }//main
}//class

結果

全ての実行時間が0.10秒以内

じゃんけんの出し方_結果.png
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

プロジェクト毎にJVMを切り替えるには

起動時、コンパイル時にJDK,JVMを切り替えたい

Javaの勉強をしているとプロジェクトによって別のバージョンを使いたい時がある。例えば私は最新のjava10以降のvar型推論が好きだけど、業務ではjava8しか使えない。常駐先によってはjava4のところも
あった。
あえて古い環境で勉強しないといけない事もあるわけです。
というより2020年現在、業務でjava10なんてまず使えない・・
eclipseとかだとjvmの指定する画面があるけど、実際コマンドラインではどうやって指定するのだろうか、という実験です。

java -version でどこのJVMを読み込んでいるか確認する

java12でコンパイル済みのMain classがあり、
環境変数JAVAHOME には java12のJVMへのパスが設定されているとする。
その状態で任意のディレクトリにjava8のJVMのJVMを配置し、その中のjavaコマンドがあるディレクトリに入って

bash
java -version 

コマンドを叩く。
すると

java version "12.0.1" 2019-04-16

と表示された。つまりカレントディレクトリであっても環境設定JAVAHOMEのクラスパスを通したJVMが動いていることになる。

そこで、

bash
./java -version 

と明示的にカレントディレクトリのjavaコマンドを指定してみた。すると、

java version "1.8.0_144"

とカレントディレクトリのJVMが起動した。
ということはjavaコマンドをフルパスか相対パスにすればJVMをどこに配置してもパスを通せるのでは?

ということで、プロジェクトルートの直下にJVMを置いた状態でプロジェクトルートで以下のコマンドを打ってみた

bash
./jdk1.8.0_144.jdk/Contents/Home/bin/java -cp ./bin Main

すると

Exception in thread "main" java.lang.UnsupportedClassVersionError: Main has been compiled by a more recent version of the Java Runtime

こんなエラーが出た。このMainクラスはjava12でビルドされているのでjava8では動かせないと言われている。つまり切り替え成功です。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

シンプレックス法をGUIでシミュレーションする

1.プログラムの概要

 このプログラムは、線形計画法における、最大化関数の最適解を求めるプログラムである。線形計画法(最大化の場合)では、最大化関数と制約条件がある。最大化関数は、1次関数であり、最大化を目的とする関数である。制約条件は、1次不等式や1次等式であり、最大化関数の解の範囲を定める式である。ただし、このプログラムでは、最大化関数も制約条件もそれぞれ2変数(X,Y)とする(このプログラムにおける、最大化関数と制約条件の詳細については、3章の操作方法を参照)。最適解を求めるアルゴリズムとして、シンプレックス法がある。シンプレックス法は、実行可能領域のある点をスタート地点として、その点を最大化関数が大きくなる方に移動させることを繰り返す。点の移動が止まったときに、最大化関数が最適解となる。このプログラムでは、ユーザが最大化関数と制約条件を入力して定める。そして、入力された最大化関数と制約条件をもとに、シンプレックス法による点の移動をシミュレーションし、最適解を求め表示させる。ただし、このプログラムは、あくまでGUIでシンプレックス法の点の移動をシミュレーションしているだけであり、実際にシンプレックス法を使って最適解を求めている訳ではない。最適解については、実行可能領域の各頂点の座標を最大化関数に代入し、最も大きい値となった解を最適解として表示させている。

2.プログラムの構造

 プログラムの構造は以下の通りである。
 1.Simplex_simuクラス
  1.mainメソッド
 2.MyPanel7クラス
  1.コンストラクタ
  2.paintComponentメソッド
  3.runメソッド
  4.actionPerformedメソッド

3.クラスの処理内容

それぞれの処理内容について説明していく。コード(5章)と見比べながら、読んで頂きたい。

3.1.Simplex_simuクラス

このクラスのmainメソッドでは、MyPanel7クラスの機能によって表示されたパネルをウィンドウに貼り付けている。また、ユーザが操作できるように、ウィンドウの大きさや閉じるボタンが押された時の処理の設定、ウィンドウを可視状態にするなど、ウィンドウの基本的な処理を記述している。

3.2.MyPanel7クラス

 コンストラクタでは、最大化関数と制約条件を入力するためのテキストフィールドを宣言・配置している。また、ユーザが、どこに何を入力すれば良いかを、わかりやすくするために、入力を促すためのラベルも宣言・配置している。そして、入力した値をプログラムに反映させ、グラフに直線を書き込むためのボタン(button1)、シミュレーションを開始させるためのボタン(button2)の、2つのボタンも宣言・配置している。このように、コンストラクタでは、主に画面内の入力部分について記述している。
 actionPerformedメソッドでは、ボタンがクリックされた時の処理を記述している。button1がクリックされた場合、テキストフィールドから値を取り出し、配列infoに格納させる。button2がクリックされた場合、シンプレックス法のシミュレーションを開始する。
 runメソッドでは、スレッドを100ms停止させている。100ms停止させることで、シミュレーションの際に、ユーザが点の動きを確認できるようにしている。
 そして、paintComponentメソッドでは、シミュレーション(点の移動)と最適解の計算の処理を行っている。具体的に、どのような記述をして処理を行っているかは、ソースコードのコメント文を読んで頂きたい。ここでは、大まかな処理について説明する。まず、入力された3つの制約条件から、グラフ上に3つの直線を引く。そして、3つの直線から、x切片、y切片、交点をそれぞれ求める。それらの点から、実行可能領域に含まれない点を座標(-1,-1)に設定して、除外する。ここで除外されなかった点は、実行可能領域の頂点になり、最適解の候補である。このようにして、求まった最適解の候補から、最大の解を与えるものを探索する。最大の解を与えるものが最適解となる。最適解が求まったので、後はシミュレーションを行う。まず、最大化関数の係数に着目し、係数が大きい方の変数を増やしていく。つまり、原点をスタート地点として、係数が大きい方の変数の切片まで点を移動させる。この際、その切片が最適解を与えるなら、そこで止まるようにする。そして、切片まで移動させたら、その切片から近い、2直線の交点まで移動させる。この交点が最適解を与えるなら、そこで止まるようにする。そして、さらに次の交点まで移動させる。この交点が最適解を与えるなら、そこで止まるようにする。このように、最適解となる点が実行可能領域内のどの点に位置するかによって、条件分岐し、点の動きが決まる。そして、最適解となる点で止まったら、グラフの右上に、最適解を表示させて、処理は終了である。

4.操作方法

 目的関数を、S₁X+S₂Yとした場合に、X,Yの係数となるS₁とS₂を、ユーザが入力フィールドから、入力して決めることができる。ただし、S₁とS₂に入力する値は、正の整数に限る。また、実行可能領域を定めるための制約条件は、都合上、3式に限定した。3式をαiX+βiY ≦γiとした(1≦i≦3)場合に、それぞれαi、βi、γiを、ユーザが入力フィールドから、入力して決めることができる(都合上、不等号は3式とも≦に定めた)。ただし、これらの値も、正の整数に限る。最大化関数と制約条件の入力が済んだら、ボタン「①入力値を設定し、グラフに出力」をクリックする。このボタンをクリックすることで、入力した値がプログラムの各変数に代入される。そして、制約条件が、グラフに書き出される。次に、ボタン「②シミュレーションスタート」をクリックする。このボタンをクリックすることで、原点をスタート地点として、シンプレックス法による点の移動が始まる。そして、点が止まったところの(X,Y)が、最大化関数の最適解を与える。このときの最適解がグラフの右上に表示されて、プログラムは終了する。これが、このプログラムの操作方法である。再び、数字を入力し、2つのボタンを順にクリックすることで、何度でも実行可能である。

5.コード

Simplex_simu.java
import javax.swing.*;
import java.awt.event.*;
import java.awt.*;
import java.awt.Font;

public class Simplex_simu {
    public static void main(String[] args) {
        JFrame frame = new JFrame();     //フレーム生成

        frame.setTitle("シンプレックス法 シミュレーション");       //ウィンドウタイトル
        frame.setSize(675, 680);                                      //ウィンドウサイズ
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);   //×ボタンが押された時の処理

        MyPanel panel = new MyPanel(); // MyPanelを生成
        frame.getContentPane().add(panel); // フレームにMyPanelを貼り付ける

        frame.setVisible(true);             //ウィンドウを可視状態にする
    }
}

class MyPanel extends JPanel implements Runnable,ActionListener {
    int count = 0;
    int info[] = new int[11];

    int simu_judge = 0;       //シミュレーションを開始するかどうかを判定するための変数
                              //simu_judge=1で、シミュレーションを開始する

    int scale_judge = 0;      //目盛りをグラフの軸に書き込むかどうかを判定するための変数
                              //scale_judge=1で、シミュレーションを開始する

    //円(そのときの解を示す)の中心座標
    int x = 100;
    int y = 600;

    //最適解を求めるステップ数を格納する(step=4で、最適解が求まり終了する)
    int step = 0;

    //3つの直線の交点のx,y座標を格納する変数
    int x_intersection12 = -1;   //1番目に入力した直線と2番目に入力した直線の交点のx座標
    int x_intersection13 = -1;   //1番目に入力した直線と3番目に入力した直線の交点のx座標
    int x_intersection23 = -1;   //2番目に入力した直線と3番目に入力した直線の交点のx座標
    int y_intersection12 = -1;   //1番目に入力した直線と2番目に入力した直線の交点のy座標
    int y_intersection13 = -1;   //1番目に入力した直線と3番目に入力した直線の交点のy座標
    int y_intersection23 = -1;   //2番目に入力した直線と3番目に入力した直線の交点のy座標

    //テキストフィールド(最大化関数)
    JTextField o_x = new JTextField(10);
    JTextField o_y = new JTextField(10);
    JTextField o_n = new JTextField(10);

    //テキストフィールド(制約条件1、1番目に入力する直線)
    JTextField s_x1 = new JTextField(10);
    JTextField s_y1 = new JTextField(10);
    JTextField s_n1= new JTextField(10);

    //テキストフィールド(制約条件2、2番目に入力する直線)
    JTextField s_x2 = new JTextField(10);
    JTextField s_y2 = new JTextField(10);
    JTextField s_n2 = new JTextField(10);

    //テキストフィールド(制約条件3、3番目に入力する直線)
    JTextField s_x3 = new JTextField(10);
    JTextField s_y3 = new JTextField(10);
    JTextField s_n3 = new JTextField(10);

    //ラベル(画面内で入力を促すために、案内するもの)
    JLabel label1 = new JLabel("x            +");
    JLabel label2 = new JLabel("y");
    JLabel label3 = new JLabel("");
    JLabel label4 = new JLabel("x            +");
    JLabel label5 = new JLabel("y           ≦");
    JLabel label6 = new JLabel("x            +");
    JLabel label7 = new JLabel("y           ≦");
    JLabel label8 = new JLabel("x            +");
    JLabel label9 = new JLabel("y           ≦");
    JLabel label10 = new JLabel("x≧0、y≧0");
    JLabel label11 = new JLabel("目的関数(最大化関数)");
    JLabel label12 = new JLabel("制約条件");

    //ボタン1(入力した値をプログラムに反映させ、グラフに直線を書き込む)
    JButton button1 = new JButton("①入力値を設定し、グラフに出力");
    //ボタン2(シンプレックス法のシミュレーションを開始する)
    JButton button2 = new JButton("②シミュレーションスタート  ");

    public MyPanel() {
        //イベントリスナを登録
        button1.addActionListener(this);
        button2.addActionListener(this);

        //パネルのレイアウト設定
        JPanel panel1 = new JPanel();
        panel1.setLayout(new GridLayout(1,1));
        JPanel panel2 = new JPanel();
        panel2.setLayout(new GridLayout(1,5));
        JPanel panel3 = new JPanel();
        panel3.setLayout(new GridLayout(1,1));
        JPanel panel4 = new JPanel();
        panel4.setLayout(new GridLayout(1,5));
        JPanel panel5 = new JPanel();
        panel5.setLayout(new GridLayout(1,5));
        JPanel panel6 = new JPanel();
        panel6.setLayout(new GridLayout(1,5));
        JPanel panel7 = new JPanel();
        panel7.setLayout(new GridLayout(1,1));

        //各パネルの色を白色に統一
        panel1.setBackground(Color.WHITE);
        panel2.setBackground(Color.WHITE);
        panel3.setBackground(Color.WHITE);
        panel4.setBackground(Color.WHITE);
        panel5.setBackground(Color.WHITE);
        panel6.setBackground(Color.WHITE);
        panel7.setBackground(Color.WHITE);

        //各コンポーネントを、それぞれのパネルに加える
        panel1.add(label11);

        panel2.add(o_x);
        panel2.add(label1);
        panel2.add(o_y);
        panel2.add(label2);
        panel2.add(label3);

        panel3.add(label12);

        panel4.add(s_x1);
        panel4.add(label4);
        panel4.add(s_y1);
        panel4.add(label5);
        panel4.add(s_n1);

        panel5.add(s_x2);
        panel5.add(label6);
        panel5.add(s_y2);
        panel5.add(label7);
        panel5.add(s_n2);

        panel6.add(s_x3);
        panel6.add(label8);
        panel6.add(s_y3);
        panel6.add(label9);
        panel6.add(s_n3);

        panel7.add(label10);


        //それぞれのパネルを、1つのパネルf_panel1に貼り付ける
        JPanel f_panel1 = new JPanel();
        f_panel1.setLayout(new GridLayout(9,1));
        f_panel1.add(panel1);
        f_panel1.add(panel2);
        f_panel1.add(panel3);
        f_panel1.add(panel4);
        f_panel1.add(panel5);
        f_panel1.add(panel6);
        f_panel1.add(panel7);
        f_panel1.add(button1);
        f_panel1.add(button2);

        f_panel1.setBackground(Color.WHITE);    //f_panel1を白色に設置する
        add(f_panel1);                          //f_panel1をMyPanelに加える

        setBackground(Color.WHITE);
        Thread refresh = new Thread(this);
        refresh.start();

    }
    public void paintComponent(Graphics g) {
        super.paintComponent(g);             // コンポーネントを背景色で塗りつぶす
        int seppen[] = new int[6];
        int seppen_max = 0;        //切片(x,y両方)の最大値
        int xy_max = 0;               //グラフにおけるx,yの最大値
        int x_min = 0;                 //x切片の最小値
        int y_min = 0;                 //y切片の最小値
        int n_x_min = 0;              //x切片の最小値を持つ式の番号
        int n_y_min = 0;              //y切片の最小値を持つ式の番号
        double d_masu = 0;         //1画素あたりのグラフ単位
        int select3 = 0;           //ステップ3において、まだ移動していない残りの交点に関与する直線の番号を格納する

        //一時的なx切片、y切片の値を求め、各変数に代入する(10倍かけてint型にキャストすることで、小数点1桁までを変数に代入する)
        int temporary_x1 = (int)(((double)info[4]/(double)info[2])*10);   //1番目に入力された直線のx切片
        int temporary_y1 = (int)(((double)info[4]/(double)info[3])*10);   //1番目に入力された直線のy切片
        int temporary_x2 = (int)(((double)info[7]/(double)info[5])*10);   //2番目に入力された直線のx切片
        int temporary_y2 = (int)(((double)info[7]/(double)info[6])*10);   //2番目に入力された直線のy切片
        int temporary_x3 = (int)(((double)info[10]/(double)info[8])*10);  //3番目に入力された直線のx切片
        int temporary_y3 = (int)(((double)info[10]/(double)info[9])*10);  //3番目に入力された直線のy切片

        //上で求めた切片を、配列seppen[]に格納する
        seppen[0] = temporary_x1;
        seppen[1] = temporary_y1;
        seppen[2] = temporary_x2;
        seppen[3] = temporary_y2;
        seppen[4] = temporary_x3;
        seppen[5] = temporary_y3;

        //切片の最大値を求める。
        seppen_max = seppen[0];
        for(int i=1; i<6; i++){
            if(seppen[i] > seppen_max){
                seppen_max = seppen[i];
            }
        }

        //切片の最大値から、グラフの目盛りの最大値を求める。
        while(true){
            //グラフの目盛りの最大値が、切片の最大値を超えた場合
            if(xy_max >= seppen_max){
                break;                //ループを抜け出し、その時点でのxy_maxを目盛りの最大値とする
            }
            xy_max = xy_max + 150;    //xy_maxを150ずつ増やす
                                      //(切片は上部で10倍されているので、実際は目盛りの最大値は15ずつ増える)
        }

        //1画素あたりのグラフ単位を求める
        d_masu = ((double)300)/((double)xy_max)*10;     //300はグラフの縦方向の画素数
                                                        //300を目盛りの最大値で割り、10倍かけることで、グラフ単位の値が求まる

        //各切片のグラフでの座標を求める
        int decisive_x1 = (int)(((double)info[4]/(double)info[2])*d_masu);   //1番目に入力された直線のx切片
        int decisive_y1 = (int)(((double)info[4]/(double)info[3])*d_masu);   //1番目に入力された直線のy切片
        int decisive_x2 = (int)(((double)info[7]/(double)info[5])*d_masu);   //2番目に入力された直線のx切片
        int decisive_y2 = (int)(((double)info[7]/(double)info[6])*d_masu);   //2番目に入力された直線のy切片
        int decisive_x3 = (int)(((double)info[10]/(double)info[8])*d_masu);  //3番目に入力された直線のx切片
        int decisive_y3 = (int)(((double)info[10]/(double)info[9])*d_masu);  //3番目に入力された直線のy切片

        //入力する際の注意事項
        g.drawString("※全ての入力フィールドには、必ず正の整数を入力すること",120,640);


        g.drawLine(100,275,100,600);    //y軸
        g.drawLine(100,600,425,600);    //x軸

        g.drawLine(95,300,105,300);     //y軸の目盛り(上から1番目)
        g.drawLine(95,400,105,400);     //y軸の目盛り(上から2番目)
        g.drawLine(95,500,105,500);     //y軸の目盛り(上から3番目)

        g.drawLine(200,595,200,605);    //x軸の目盛り(左から1番目)
        g.drawLine(300,595,300,605);    //x軸の目盛り(左から2番目)
        g.drawLine(400,595,400,605);    //x軸の目盛り(左から3番目)

        //y軸矢印
        g.drawLine(100,275,95,280);
        g.drawLine(100,275,105,280);
        //x軸矢印
        g.drawLine(425,600,420,595);
        g.drawLine(425,600,420,605);

        //入力された3本の直線をグラフの描画
        g.drawLine(decisive_x1+100,600,100,600-decisive_y1);
        g.drawLine(decisive_x2+100,600,100,600-decisive_y2);
        g.drawLine(decisive_x3+100,600,100,600-decisive_y3);

        //各目盛りの値を計算する
        int n_xy1 = xy_max/30;
        int n_xy2 = (xy_max/30)*2;
        int n_xy3 = xy_max/10;

        //各目盛りの数値を、文字列に変換する(各目盛りの数値の桁数を数えるため)
        String s_xy1 = String.valueOf(n_xy1);
        String s_xy2 = String.valueOf(n_xy2);
        String s_xy3 = String.valueOf(n_xy3);

        //各目盛りの数値の桁数を取得し、代入する(桁数によって、目盛りの数値の位置が変わらないようにするため)
        int xy1_length = String.valueOf(n_xy1).length();
        int xy2_length = String.valueOf(n_xy2).length();
        int xy3_length = String.valueOf(n_xy3).length();

        //bottan1が入力された場合(各目盛りの値をグラフに書き込む)
        if(scale_judge == 1){
            g.drawString(s_xy1, 200-(xy1_length*3), 620);
            g.drawString(s_xy2, 300-(xy2_length*3), 620);
            g.drawString(s_xy3, 400-(xy3_length*3), 620);
            g.drawString(s_xy1, 90-(xy1_length*6), 505);
            g.drawString(s_xy2, 90-(xy2_length*6), 405);
            g.drawString(s_xy3, 90-(xy3_length*6), 305);
        }

        //x切片とy切片の最小値を求める。また、その最小値を持つ直線の番号(何番目に入力された)を求める
        x_min = decisive_x1;
        n_x_min = 1;
        y_min = decisive_y1;
        n_y_min = 1;

        if(x_min > decisive_x2){
            x_min = decisive_x2;
            n_x_min = 2;
        }
        if(x_min > decisive_x3){
            x_min = decisive_x3;
            n_x_min = 3;
        }


        if(y_min > decisive_y2){
            y_min = decisive_y2;
            n_y_min = 2;
        }
        if(y_min > decisive_y3){
            y_min = decisive_y3;
            n_y_min = 3;
        }

        //各直線(制約条件による直線)の上限の値(定数項)を、グラフ単位の値に変換し、代入する
        int upper1 = (int)(((double)info[4])*d_masu);
        int upper2 = (int)(((double)info[7])*d_masu);
        int upper3 = (int)(((double)info[10])*d_masu);


        //各直線の交点のx座標を求める
        x_intersection12 = (int)((((double)info[7]/(double)info[6])-((double)info[4]/(double)info[3]))/(((double)info[5]/(double)info[6])-((double)info[2]/(double)info[3]))*d_masu);
        x_intersection13 = (int)((((double)info[10]/(double)info[9])-((double)info[4]/(double)info[3]))/(((double)info[8]/(double)info[9])-((double)info[2]/(double)info[3]))*d_masu);
        x_intersection23 = (int)((((double)info[10]/(double)info[9])-((double)info[7]/(double)info[6]))/(((double)info[8]/(double)info[9])-((double)info[5]/(double)info[6]))*d_masu);

        //各直線の交点のy座標を求める
        y_intersection12 = (int)((((double)info[7]/(double)info[5])-((double)info[4]/(double)info[2]))/(((double)info[6]/(double)info[5])-((double)info[3]/(double)info[2]))*d_masu);
        y_intersection13 = (int)((((double)info[10]/(double)info[8])-((double)info[4]/(double)info[2]))/(((double)info[9]/(double)info[8])-((double)info[3]/(double)info[2]))*d_masu);
        y_intersection23 = (int)((((double)info[10]/(double)info[8])-((double)info[7]/(double)info[5]))/(((double)info[9]/(double)info[8])-((double)info[6]/(double)info[5]))*d_masu);

        //交点がない場合、その座標を(-1,-1)に設定する。(-1,-1)は実行可能領域の範囲外
        //1と2の直線が平行な場合
        if(x_intersection12>2000000000 || y_intersection12>2000000000){
            x_intersection12 = -1;
            y_intersection12 = -1;
        }
        //1と3の直線が平行な場合
        if(x_intersection13>2000000000 || y_intersection13>2000000000){
            x_intersection13 = -1;
            y_intersection13 = -1;
        }
        //2と3の直線が平行な場合
        if(x_intersection23>2000000000 || y_intersection23>2000000000){
            x_intersection23 = -1;
            y_intersection23 = -1;
        }

        //1と2の交点が実行可能領域の範囲外にある場合
        if(info[8]*x_intersection12+info[9]*y_intersection12 > upper3){
            x_intersection12 = -1;
            y_intersection12 = -1;
        }
        //1と3の交点が実行可能領域の範囲外にある場合
        if(info[5]*x_intersection13+info[6]*y_intersection13 > upper2){
            x_intersection13 = -1;
            y_intersection13 = -1;
        }
        //2と3の交点が実行可能領域の範囲外にある場合
        if(info[2]*x_intersection23+info[3]*y_intersection23 > upper1){
            x_intersection23 = -1;
            y_intersection23 = -1;
        }


        int answer_x = info[0]*x_min;    //x切片(最小値)での解
        int answer_y = info[1]*y_min;    //x切片(最小値)での解

        //各直線の交点での解(初期値は-1)
        int answer12 = -1;
        int answer13 = -1;
        int answer23 = -1;

        //各直線の交点での解が、実行可能領域内にある場合に、その解を各変数に代入する
        if(x_intersection12>0 && y_intersection12>0){
            answer12 = info[0]*x_intersection12+info[1]*y_intersection12;
        }
        if(x_intersection13>0 && y_intersection13>0){
            answer13 = info[0]*x_intersection13+info[1]*y_intersection13;
        }
        if(x_intersection23>0 && y_intersection23>0){
            answer23 = info[0]*x_intersection23+info[1]*y_intersection23;
        }

        //上で求めた解の中から、最大値の解(最適解)を探し、answer_maxに代入する
        int answer_max = answer_x;
        if(answer_max < answer_y){
            answer_max = answer_y;
        }
        if(answer_max < answer12){
            answer_max = answer12;
        }
        if(answer_max < answer13){
            answer_max = answer13;
        }
        if(answer_max < answer23){
            answer_max = answer23;
        }

        //シミュレーションを開始するボタンがクリックされた場合
        if(simu_judge == 1){

            g.setColor(Color.red);          //描画する円の色を赤色に設定する

            g.fillOval(x-4, y-4, 8, 8);     //円を描画する((x-4,y-4)が円の中心)
            //ステップ1(原点から切片の移動)
            if(step == 1){
                //目的関数のy切片の係数の方が大きい場合
                if(info[0] <= info[1]){
                    y = y-1;              //原点からy軸に沿って移動する
                    //点がy切片の最小値に到達した場合
                    if(y == 600-y_min){
                        //その点が最適解を与える場合
                        if(answer_max == answer_y){
                            step = 4;          //ステップ4に移る
                        }
                        //その点が最適解を与えない場合
                        else{
                            step = 2;          //ステップ2に移る
                        }
                    }
                }
                //目的関数のy切片の係数の方が小さい場合
                else if(info[0] > info[1]){
                    x = x+1;              //原点からx軸に沿って移動する
                    //点がx切片の最小値に到達した場合
                    if(x == x_min+100){
                        //その点が最適解を与える場合
                        if(answer_max == answer_x){
                            step = 4;         //ステップ4に移る
                        }
                        //その点が最適解を与えない場合
                        else{
                            step = 2;         //ステップ2に移る
                        }
                    }
                }
            }
            //ステップ2(切片から交点への移動)
            else if(step == 2){
                //目的関数のy切片の係数の方が大きい場合
                if(info[0] <= info[1]){
                    //直線1,2の交点に到達した場合(実行可能領域の範囲内に限る)
                    if(x_intersection12>0 && x>x_intersection12+100){
                        //到達した際に、丁度その点が円の中心座標になるように、補正する
                        x = x-(x - (x_intersection12+100));
                        y = y-(y - (600-y_intersection12));
                        //その点が最適解を与える場合
                        if(answer12 > answer13 && answer12 > answer23){
                            step = 4;          //ステップ4に移る
                        }
                        //その点が最適解を与えない場合
                        else{
                            step = 3;       //ステップ3に移る
                            select3 = 1;    //残りの交点を探す手がかりとして、直線1を記憶する
                        }
                    }
                    //直線1,3の交点に到達した場合(実行可能領域の範囲内に限る)
                    else if(x_intersection13>0 && x>x_intersection13+100){
                        //到達した際に、丁度その点が円の中心座標になるように、補正する
                        x = x-(x - (x_intersection13+100));
                        y = y-(y - (600-y_intersection13));
                        //その点が最適解を与える場合
                        if(answer13 > answer12 && answer13 > answer23){
                            step=4;          //ステップ4に移る
                        }
                        //その点が最適解を与えない場合
                        else{
                            step = 3;       //ステップ3に移る
                            select3 = 2;    //残りの交点を探す手がかりとして、直線2を記憶する
                        }
                    }
                    //直線2,3の交点に到達した場合(実行可能領域の範囲内に限る)
                    else if(x_intersection23>0 && x>x_intersection23+100){
                        //到達した際に、丁度その点が円の中心座標になるように、補正する
                        x = x-(x - (x_intersection23+100));
                        y = y-(y - (600-y_intersection23));
                        //その点が最適解を与える場合
                        if(answer23 > answer12 && answer23 > answer13){
                            step=4;          //ステップ4に移る
                        }
                        //その点が最適解を与えない場合
                        else{
                            step = 3;       //ステップ3に移る
                            select3 = 3;    //残りの交点を探す手がかりとして、直線3を記憶する
                        }
                    }
                    //最適解がx切片で、その点に到達した場合
                    else if(answer_max == answer_x && y>600){
                        //到達した際に、丁度その点が円の中心座標になるように、補正する
                        x = x-(x - (x_min+100));
                        y = y-(y - 600);
                        step = 4;          //ステップ4に移る
                    }

                    //この3つの分岐は、各直線に対応した座標の変位を与える(その直線上を移動するように)
                    else if(n_y_min == 1){
                        x = x+info[3];
                        y = y+info[2];
                    }
                    else if(n_y_min == 2){
                        x = x+info[6];
                        y = y+info[5];
                    }
                    else if(n_y_min == 3){
                        x = x+info[9];
                        y = y+info[8];
                    }
                }
                //目的関数のy切片の係数の方が小さい場合
                else if(info[0] > info[1]){
                    //直線1,2の交点に到達した場合(実行可能領域の範囲内に限る)
                    if(y_intersection12>0 && y<600-y_intersection12){
                        //到達した際に、丁度その点が円の中心座標になるように、補正する
                        x = x + ((x_intersection12+100)-x);
                        y = y + ((600-y_intersection12)-y);
                        //その点が最適解を与える場合
                        if(answer12 > answer13 && answer12 > answer23){
                            step=4;          //ステップ4に移る
                        }
                        //その点が最適解を与えない場合
                        else{
                            step=3;         //ステップ3に移る
                            select3 = 1;    //残りの交点を探す手がかりとして、直線1を記憶する
                        }
                    }
                    //直線1,3の交点に到達した場合(実行可能領域の範囲内に限る)
                    else if(y_intersection13>0 && y<600-y_intersection13){
                        //到達した際に、丁度その点が円の中心座標になるように、補正する
                        x = x + ((x_intersection13+100)-x);
                        y = y + ((600-y_intersection13)-y);
                        //その点が最適解を与える場合
                        if(answer13 > answer12 && answer13 > answer23){
                            step=4;          //ステップ4に移る
                        }
                        //その点が最適解を与えない場合
                        else{
                            step=3;         //ステップ3に移る
                            select3 = 2;    //残りの交点を探す手がかりとして、直線2を記憶する
                        }
                    }
                    //直線2,3の交点に到達した場合(実行可能領域の範囲内に限る)
                    else if(y_intersection23>0 && y<600-y_intersection23){
                        //到達した際に、丁度その点が円の中心座標になるように、補正する
                        x = x + ((x_intersection23+100)-x);
                        y = y + ((600-y_intersection23)-y);
                        //その点が最適解を与える場合
                        if(answer23 > answer12 && answer23 > answer13){
                            step=4;          //ステップ4に移る
                        }
                        //その点が最適解を与えない場合
                        else{
                            step=3;         //ステップ3に移る
                            select3 = 3;    //残りの交点を探す手がかりとして、直線3を記憶する
                        }
                    }
                    //最適解がy切片で、その点に到達した場合
                    else if(answer_max == answer_y && x<100){
                        //到達した際に、丁度その点が円の中心座標になるように、補正する
                        x = x + (100-x);
                        y = y + ((600-y_min)-y);
                        step = 4;          //ステップ4に移る
                    }

                    //この3つの分岐は、各直線に対応した座標の変位を与える(その直線上を移動するように)
                    else if(n_x_min == 1){
                        x = x-info[3];
                        y = y-info[2];
                    }
                    else if(n_x_min == 2){
                        x = x-info[6];
                        y = y-info[5];
                    }
                    else if(n_x_min == 3){
                        x = x-info[9];
                        y = y-info[8];
                    }
                }
            }
            //ステップ3(交点から交点への移動)
            else if(step == 3){
                //目的関数のy切片の係数の方が大きい場合
                if(info[0] <= info[1]){
                    //残りの交点である、直線1,3の交点に到達し、その点が実行解である場合
                    if(select3 == 1 && x_intersection13>0 && x>x_intersection13+100){
                        //到達した際に、丁度その点が円の中心座標になるように、補正する
                        x = x-(x - (x_intersection13+100));
                        y = y-(y - (600-y_intersection13));
                        step=4;          //ステップ4に移る
                    }
                    //残りの交点である、直線2,3の交点に到達し、その点が実行解である場合
                    else if(select3 == 1 && x_intersection23>0 && x>x_intersection23+100){
                        //到達した際に、丁度その点が円の中心座標になるように、補正する
                        x = x-(x - (x_intersection23+100));
                        y = y-(y - (600-y_intersection23));
                        step=4;          //ステップ4に移る
                    }
                    //残りの交点である、直線1,2の交点に到達し、その点が実行解である場合
                    else if(select3 == 2 && x_intersection12>0 && x>x_intersection12+100){
                        //到達した際に、丁度その点が円の中心座標になるように、補正する
                        x = x-(x - (x_intersection12+100));
                        y = y-(y - (600-y_intersection12));
                        step=4;          //ステップ4に移る
                    }
                    //残りの交点である、直線2,3の交点に到達し、その点が実行解である場合
                    else if(select3 == 2 && x_intersection23>0 && x>x_intersection23+100){
                        //到達した際に、丁度その点が円の中心座標になるように、補正する
                        x = x-(x - (x_intersection23+100));
                        y = y-(y - (600-y_intersection23));
                        step=4;          //ステップ4に移る
                    }
                    //残りの交点である、直線1,2の交点に到達し、その点が実行解である場合
                    else if(select3 == 3 && x_intersection12>0 && x>x_intersection12+100){
                        //到達した際に、丁度その点が円の中心座標になるように、補正する
                        x = x-(x - (x_intersection12+100));
                        y = y-(y - (600-y_intersection12));
                        step=4;          //ステップ4に移る
                    }
                    //残りの交点である、直線1,3の交点に到達し、その点が実行解である場合
                    else if(select3 == 3 && x_intersection13>0 && x>x_intersection13+100){
                        //到達した際に、丁度その点が円の中心座標になるように、補正する
                        x = x-(x - (x_intersection13+100));
                        y = y-(y - (600-y_intersection13));
                        step=4;          //ステップ4に移る
                    }

                    //この3つの分岐は、各直線に対応した座標の変位を与える(その直線上を移動するように)
                    else if(n_y_min == 1){
                        if(n_x_min == 2){
                            x = x+info[9];
                            y = y+info[8];
                        }
                        else if(n_x_min == 3){
                            x = x+info[6];
                            y = y+info[5];
                        }
                    }
                    else if(n_y_min == 2){
                        if(n_x_min == 1){
                            x = x+info[9];
                            y = y+info[8];
                        }
                        else if(n_x_min == 3){
                            x = x+info[3];
                            y = y+info[2];
                        }
                    }
                    else if(n_y_min == 3){
                        if(n_x_min == 1){
                            x = x+info[6];
                            y = y+info[5];
                        }
                        else if(n_x_min == 2){
                            x = x+info[3];
                            y = y+info[2];
                        }
                    }
                }
                //目的関数のy切片の係数の方が小さい場合
                else if(info[0] > info[1]){
                    if(select3 == 1 && y_intersection13>0 && y<600-y_intersection13){
                        //到達した際に、丁度その点が円の中心座標になるように、補正する
                        x = x + ((x_intersection13+100)-x);
                        y = y + ((600-y_intersection13)-y);
                        step=4;          //ステップ4に移る
                    }
                    else if(select3 == 1 && y_intersection23>0 && y<600-y_intersection23){
                        //到達した際に、丁度その点が円の中心座標になるように、補正する
                        x = x + ((x_intersection23+100)-x);
                        y = y + ((600-y_intersection23)-y);
                        step=4;          //ステップ4に移る
                    }
                    else if(select3 == 2 && y_intersection12>0 && y<600-y_intersection12){
                        //到達した際に、丁度その点が円の中心座標になるように、補正する
                        x = x + ((x_intersection12+100)-x);
                        y = y + ((600-y_intersection12)-y);
                        step=4;          //ステップ4に移る
                    }
                    else if(select3 == 2 && y_intersection23>0 && y<600-y_intersection23){
                        //到達した際に、丁度その点が円の中心座標になるように、補正する
                        x = x + ((x_intersection23+100)-x);
                        y = y + ((600-y_intersection23)-y);
                        step=4;          //ステップ4に移る
                    }
                    else if(select3 == 3 && y_intersection12>0 && y<600-y_intersection12){
                        //到達した際に、丁度その点が円の中心座標になるように、補正する
                        x = x + ((x_intersection12+100)-x);
                        y = y + ((600-y_intersection12)-y);
                        step=4;          //ステップ4に移る
                    }
                    else if(select3 == 3 && y_intersection13>0 && y<600-y_intersection13){
                        //到達した際に、丁度その点が円の中心座標になるように、補正する
                        x = x + ((x_intersection13+100)-x);
                        y = y + ((600-y_intersection13)-y);
                        step=4;          //ステップ4に移る
                    }

                    //この3つの分岐は、各直線に対応した座標の変位を与える(その直線上を移動するように)
                    else if(n_x_min == 1){
                        if(n_y_min == 2){
                            x = x-info[9];
                            y = y-info[8];
                        }
                        else if(n_y_min == 3){
                            x = x-info[6];
                            y = y-info[5];
                        }
                    }
                    else if(n_x_min == 2){
                        if(n_y_min == 1){
                            x = x-info[9];
                            y = y-info[8];
                        }
                        else if(n_y_min == 3){
                            x = x-info[3];
                            y = y-info[2];
                        }
                    }
                    else if(n_x_min == 3){
                        if(n_x_min == 1){
                            x = x-info[6];
                            y = y-info[5];
                        }
                        else if(n_x_min == 2){
                            x = x-info[3];
                            y = y-info[2];
                        }
                    }
                }
            }
            //ステップ4(点(円)の移動が止まり、最適解が求まる。最適解を表示する)
            else if(step == 4){
                //表示する最適解の値の色を、黒色に設定する
                g.setColor(Color.black);

                //フォントサイズを設定し、見えやすいようにする
                Font fo = new Font("Serif",Font.PLAIN,20);
                g.setFont(fo);

                //グラフ単位の最適解を、問題の単位に直す
                double best_answer = ((double)answer_max/d_masu);
                //最適解の値を文字列に変換する
                String ba = String.valueOf(best_answer);

                //最適解を表示する
                g.drawString("最適解はおおよそ",450,325);
                g.drawString(String.format("%.2f", best_answer), 475, 350);
            }

        }




    }

    //円を動かすために、スレッドの実行間隔を制御する
    public void run() {
        while(true) {
            repaint();
            try {
                Thread.sleep(300);       //スレッドを100ms停止する
            }catch(Exception e) {
                System.out.println(e);
            }
        }
    }

    public void actionPerformed(ActionEvent ae){
        //button1がクリックされた場合
        if(ae.getSource() == button1){
            //テキストフィールドから入力された値を、配列infoに格納する
            info[0] = Integer.parseInt(o_x.getText());
            info[1] = Integer.parseInt(o_y.getText());
            info[2] = Integer.parseInt(s_x1.getText());
            info[3] = Integer.parseInt(s_y1.getText());
            info[4] = Integer.parseInt(s_n1.getText());
            info[5] = Integer.parseInt(s_x2.getText());
            info[6] = Integer.parseInt(s_y2.getText());
            info[7] = Integer.parseInt(s_n2.getText());
            info[8] = Integer.parseInt(s_x3.getText());
            info[9] = Integer.parseInt(s_y3.getText());
            info[10] = Integer.parseInt(s_n3.getText());    
            scale_judge = 1;     //scale_judge=1に設定し、グラフに制約条件の3つの直線を書き込む
        }
        //button2がクリックされた場合
        else if(ae.getSource() == button2){
            //円の座標を、グラフの原点に設定する
            x = 100;
            y = 600;

            step = 1;          //シミュレーションのステップを1に設定する
            simu_judge = 1;    //simu_judgeを1に設定し、シミュレーションを開始する
        }
    }
}

6.イメージ

image.png

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Android] TextView の textScaleX を自動調整する

何を作ったか

限られたスペースしか使えない状況で TextView に長い文字列を表示させようとして、途中で文字列が切れてしまい見た目がよろしくない経験は多々あるでしょう。解決策はいろいろあるでしょうが、ここでは textScaleX と呼ばれる TextView の属性に注目します。表示する文字の横方向への伸縮倍率を表す値であり、1より大きい値なら文字列を伸ばし、逆に1より小さな値なら文字列を収縮させます。TextView の最大横幅に収まり切らない長さの文字列を表示するときに、この textScaleX の値を自動で調節して文字列の長さを圧縮する View を作りました。クラス名は ExpandableTextView とでも呼びましょう。

デモ画像

分かりやすさのため、ExpandableTextView の背景を灰色にしてあります。
expandableTextView_demo.gif

何が出来るか

  • View の最大横幅に合わせて表示する文字列を横方向へ伸縮する(textScaleX = (0.0, 1.0])
  • onLayout() や setText() で状況が変化するたびに自動で調整する
  • textScaleX の下限値を用意して、文字列を最小倍率まで圧縮しても収まらない場合は代替文字列に置換する(末尾を削って"…"に置換)

View 横幅の自動調整の挙動

LayoutParam の layout_width に関して場合分け。XMLで静的に定義する android:layout_width の値のことです。

  • wrap_content
    • 文字列の幅 > View の最大幅:文字列を収縮(textScaleX < 1)させる
    • 文字列の幅 <= View の最大幅:文字列長さ(textScaleX = 1)に View 幅を合わせる
    • ただし、View の横幅の最大値 android:maxWidth を上限とする
  • それ以外:LayoutParam から計算される値で横幅を決定し、この長さを超える文字列は横方向に縮小させて収める

ソースコード

Github に上げておきます。

実装の説明

SDK version = 26 で確認

TextView を継承する

楽に実装したいので TextView を継承して弄り回します。いくつかのメソッドをオーバーライドして振る舞いを改変。

  • setText(CharSequence,BufferType)
    いくつかオーバロードが存在しますが setText(char[],int,int) 以外はすべてここを経由して呼び出されます。残念ながら setText(char[],int,int) は final でオーバーライドできないので、諦めて対象外とします。
  • setMaxWidth(int)
    親クラスのメンバ mMaxWidth が private なので、この setter を通じて指定される値を自身でも記憶しておきます。
  • setLines(int)
    長い文字列を伸縮しつつ1行で表示するための View ですから、行数は1に強制します。
  • setTextScaleX(float)
    View 側で自動で調節するのでユーザには弄らせないように変更。

表示する文字列の制御

許される最大横幅に対し、必要に応じて文字列を伸縮させたり代替文字列に置換する制御を用意

ExpandableTextView#updateText
private Paint mTextPaint;           // 文字を描画するオブジェクト
private CharSequence mDisplayedText // 実際に表示している文字列

private void updateText(CharSequence text, BufferType type, int widthSize){
    mTextPaint.setTextScaleX(1f);
    float length = mTextPaint.measureText(text, 0, text.length());
    int padding = getCompoundPaddingLeft() + getCompoundPaddingRight();
    if ( length + padding > widthSize ){
        float scale = (widthSize - padding) / length;
        text =  modifyText(scale, text, widthSize - padding);
    }
    mDisplayedText = text;
    super.setText(text, type);
}

/**
 * 指定された最大幅に合うように文字列を横方向へ収縮させる.
 * 収縮率が{@link #mMinTextScaleX}を下回る場合は、その最小比率でちょうど最大幅に合うような代替文字列に置換する
 * @param scale     rough value, with which text width can be adjusted to maxLength
 * @param text      raw text
 * @param maxLength max length of text
 * @return modified text
 */
private CharSequence modifyText(float scale, CharSequence text, float maxLength){
    // ソースコード参照
}

具体的に文字列を収縮したり、代替文字列に置換するのは modifyText() 以下になります。注意すべき点として、Paint#measureText で測定される文字列の長さは、

  • Paint#setTextScaleX で指定した比率とは比例しない
  • 文字列の文字数 String#length とは比例しない

ですから、目的の横幅になる倍率 textScaleX を探索するために、適当な当たりをつけて倍率を少しずつ変化させ最善の値を採択します。同様に、最大横幅に文字列が収まる代替文字列を決定するために、1文字ずつ末端から削りながら探索する、という残念な実装方法になってしまいました。コードは汚いのでここには載せません。

View#onMeasure(int,int) で View の横幅を制御する

親の View がこの View を配置するとき、その大きさを計測するために呼び出されるメソッドです。onMeasure()の役割に関しては [Qiita]onMeasureとonLayoutについて理解する が詳しいです。今回興味があるのは横幅に関してのみであり、width に関する measureSpec に要求を追加します。onMeasure() をすべて自前で実装するのは面倒なので、横幅以外はそのまま super#onMeasure() に渡して親クラスに丸投げします。

ExpandableTextView#onMeasure
private Paint mTextPaint;           // 文字を描画するオブジェクト
private CharSequence mCurrentText;  // 表示したい文字列
private CharSequence mDisplayedText;// 実際に表示している文字列
private int mMaxWidth;              // 最大の横幅
private int mRawWidthMeasureSpec;   // 親から指定されたこのViewの大きさに関する要求

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){
    // 幅に関してのみ要求を追加 
    int widthMode = MeasureSpec.getMode(widthMeasureSpec);
    int widthSize = MeasureSpec.getSize(widthMeasureSpec);

    CharSequence text = mCurrentText;
    mTextPaint.setTextScaleX(1f);
    float length = mTextPaint.measureText(text, 0, text.length());

    int padding = getCompoundPaddingLeft() + getCompoundPaddingRight();
    int requestedWidth = 0;

    switch ( widthMode ){
        case MeasureSpec.EXACTLY:
            // 表示する文字列に関わらず指定された横幅で固定
            requestedWidth = widthSize;
            // 固定された横幅に合わせて表示する文字列を調整
            updateText(mCurrentText, BufferType.NORMAL, widthSize);
            break;
        case MeasureSpec.AT_MOST:
            int max = Math.min(widthSize, mMaxWidth);
            if ( length + padding > max ){
                // 最大の横幅に収まり切らないなら圧縮
                requestedWidth = max;
                float scale = (max - padding) / length;
                CharSequence modified = modifyText(scale, text, max - padding);
                if ( !mDisplayedText.equals(modified) ){
                    mDisplayedText = modified;
                    super.setText(modified, BufferType.NORMAL);
                }
            }else{
                // 収まるなら文字列の幅にViewの横幅を合わせる
                requestedWidth = (int)Math.ceil(length + padding);
            }
            break;
        case MeasureSpec.UNSPECIFIED:
            // 指定がないなら勝手に決める
            requestedWidth = (int)Math.ceil(length + padding);
            mTextPaint.setTextScaleX(1f);
            mDisplayedText = text;
            break;
    }

    mRawWidthMeasureSpec = widthMeasureSpec;

    // 横幅は固定値として要求を追加する
    int calcWidthMeasureSpec = MeasureSpec.makeMeasureSpec(requestedWidth, MeasureSpec.EXACTLY);

    // onMeasure()の細かい実装は親に任せる
    super.onMeasure(calcWidthMeasureSpec, heightMeasureSpec);
}

表示する文字列・環境の変化に対応する

setText() で表示したい文字列が変化した場合などでも意図したとおり振る舞うための実装。ここで重要な点は TextView が layout() を呼び出すタイミング。通常の TextView はその大きさが中身に依存する場合(LayoutParamでwrap_contentを指定するなどして onMeasure()で MeasureSpec.AT_MOST が指定される場合)、

  • setText() で表示する文字列が変化する
  • setMaxWidth() で最大横幅が変化する
  • setTextScaleX() で textScaleX が変化する

のタイミングで layout() を呼び出し、View の大きさを再計算します。今回は上記に加えて、

  • setMinTextScaleX() で textScaleX の最小値が変化する

のタイミングでも layout() を呼び出すように実装します。

以上は View の大きさが中身に依存する場合の話です。親から MeasureSpec.EXACTLY で横幅が指定されている場合は、updateText() で表示する文字列・伸縮倍率を固定幅に合わせて調節します。

ExpandableTextView
private CharSequence mCurrentText;  // 表示したい文字列
private CharSequence mDisplayedText // 実際に表示している文字列
private int mMaxWidth;              // 最大の横幅
private int mRawWidthMeasureSpec;   // 親から指定されたこのViewの大きさに関する要求
private float mMinTextScaleX;       // textScalex の最小値

@Override
public void setMaxWidth(int width){
    // super#mMaxWidth は private だから、自身でも記録しておく
    if ( width <= 0 ) return;
    if ( width != mMaxWidth ){
        mMaxWidth = width;
        mRequestMeasure = true;
        super.setMaxWidth(width);
        // maxWidth はこのViewの横幅が中身に依存する設定(wrap_contentなど)の場合のみ有効
        // そのような場合では親クラスが layout() を呼び出す
    }
}

/**
 * 文字列の横方向のScaleの最小値を指定する.
 * @param scale in range of (0,1]
 * @see TextPaint#setTextScaleX(float)
 */
public void setMinTextScaleX(float scale){
    if ( scale <= 0 || scale > 1 ) return;
    if ( mMinTextScaleX != scale ){
        mMinTextScaleX = scale;
        if ( MeasureSpec.getMode(mRawWidthMeasureSpec) == MeasureSpec.EXACTLY ){
            // layout() する必要なし
            updateText(mCurrentText, BufferType.NORMAL, MeasureSpec.getSize(mRawWidthMeasureSpec));
        }else{
            // layout() して大きさを再計算
            requestLayout();
        }
    }
}


//#setText(char[], int, int) <- finalでオーバーライド不可
//           以外はここを経由している
@Override
public void setText(CharSequence text, BufferType type){
    if ( text == null ) return;
    if ( mCurrentText != null && mCurrentText.equals(text) ) return;

    mCurrentText = text;

    if ( MeasureSpec.getMode(mRawWidthMeasureSpec) == MeasureSpec.EXACTLY ){
        // 親クラスはlayout()しないし、する必要もない
        updateText(text, type, MeasureSpec.getSize(mRawWidthMeasureSpec));
    }else{
        // 親クラスがlayout()する
        super.setText(text, type);
    }
}

2つの setTextScaleX() の落とし穴

今回の実装では、TextView#getPaint() で取得した文字描画に使う Paint オブジェクトに setTextScaleX() で文字列の伸縮倍率を指定しています。getPaint() には Paint のプロパティを弄るなと注意書きがありますが(原文:Use this only to consult the Paint's properties and not to change them.)、textScaleX に関しては以下のような実装箇所あり。

TextView
public void setTextScaleX(float size) {
    if (size != mTextPaint.getTextScaleX()) {
        mUserSetTextScaleX = true;
        mTextPaint.setTextScaleX(size);
        if (mLayout != null) {
            nullLayouts();
            requestLayout();
            invalidate();
        }
    }
}

private void setText(CharSequence text, BufferType type, boolean notifyBefore, int oldlen) {
    /* 中略 */
    if (!mUserSetTextScaleX) mTextPaint.setTextScaleX(1.0f);   
    /* 中略 */
}

TextView の方の setTextScaleX() から指定しないと textScaleX = 1.0f で上書きされてしまう!

はじめ嵌りました。解決策:一度でも TextView#setTextScaleX() を呼び出せばいい。

ExpandableTextView
public class ExpandableTextView extends AppCompatTextView{

    public ExpandableTextView(Context context, AttributeSet attr, int defaultAttr){
        super(context, attr, defaultAttr);

        /* 中略 */

        // 現在の値と異なる値を指定すればいいので適当に getTextScaleX() / 2f
        super.setTextScaleX(getTextScaleX() / 2f);
    }
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[java] 例外をスローする

例外処理はjavaの基本であるが、認識違いをしていたので備忘録として残す。

発生した問題

メソッドの呼び出し元に例外を投げたかったので、catchした例外をスローしようとすると、なぜかコンパイルエラーになっていた。

実装

例えばタイムアウトエラーを投げる場合は以下の通りである。

    public void foo(){
        try {
            fetch();
        } catch (SocketTimeoutException e) {
        }
    }

    public Integer fetch(){
        try {
            //Http通信
        } catch (SocketTimeoutException e) {
            throw e;//ここでコンパイルエラー
        }finally {
        }
        return number;
    }

認識違い

throwのほかに
thorws句というのがあるがこれはcatch文と同じ働きがあると思っていた。しかし実際それがあるメソッドは例外を投げる可能性がるメソッドという意味であった。

そこで例外をスローしているメソッドにthrows句を付け足すとコンパイルエラーが消え、foo()でキャッチできるようになった。

感想

基礎の基礎で躓くとは思わなかったが、今のうちに知れてよかった…

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

線形探索

自己紹介

この記事が初投稿になるので簡単に自己紹介をしておこうと思います。現在(2020/02/07)大学2年生のもので理工学部で情報系の勉強を学んでいます。
記事を投稿しようとした理由は、「いつまでたっても初心者を抜け出せない!」と強く思うようになってきたため、ここらで環境を変えないと何も進展がないと思い投稿しようと思いました。Googleで「中級者 なり方」など脳筋のように検索していましたがよい結果が得られなかったため、コードたくさん書けばいんじゃね?と思いこれからたくさんコードを書いていこうと思います!初心者なのでその辺を理解したうえで温かい目で見守ってください笑

線形探索

というわけで第1回目の投稿は線形探索です。なぜ線形探索にしたかは正直適当です。細かいことは気にせず学んでいきましょう。

これはあるn個のデータが登録されているデータの中から特定のデータ(キー)を探す処理を行うアルゴリズムです。配列にデータが格納されていると仮定して、その配列の先頭から末尾までスキャンしながら要素とキーを比較していきます。今回は1~9までのデータをもっている配列に対して探索をしてみます。以下にコードを示してみます。

senkeitansaku.java
public class senkeitansaku {
    public static void main(String[] args) {
        //配列を作成
        int[] array = {1,2,3,4,5,6,7,8,9};
        int result;
        // 9を持っているインデックスを探す
        result = tansaku(array, 9);

        //探したいデータが存在するバイト存在しない場合で条件分岐
        if (result != -1) {
            System.out.println("Found: index key = " + result);
        } else {
            System.out.println("Not found.");
        }
    }

        //探したいデータを検索するメソッドを定義
    public static int tansaku(int[] array,int target) {
        int notfound = -1;
        for (int i=0;i<array.length;i++) {
            if (array[i]==target) {
                return i;
            }
        }return notfound;
    }
}

初めての投稿なのでこの程度の記事ですがこれからもたくさん投稿して脱初心者を目指して頑張っていこうと思います!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む