Android PDFの編集・保存・印刷その後3
自分の世界に入っている柴犬です。自然というかボーとしている顔しています。
散歩の途中で目の前で何か流星のように横切るものがありました。目を凝らしてみると燕でした。よくもあんなスピードで障害物を避けることができるなと感心です。視界を横切るスピードは全く流星と変わりません。。
概要
これまで表題のことについて考えてきましたが、今回はアプリからサーバーにPDFをリクエストして、アプリからPDFを印刷できないか考えてみました。
サーバーからはPDFファイルをBase64で文字列に変換してリスエストのレスポンスの中でその文字を返す手続きにしました。
いろいろ試行錯誤してみて形だけはできたみたいなので記録します。
2024年2月26日現在
WEBのみでは断片的で覚えにくいので最初に購入した Kotlin の本です。
class requestPDF
サーバーに GET でリクエストしてResponse の中で PDFファイルをバイト列に分解してBase64でエンコードしたJSON文字列を 受け取ります。
受け取った Response を文字列にして返します。
private String getPost(Map<String, String> postbody) throws IOException {
int TIMEOUT_MILLIS = 10000;
StringBuffer sb = new StringBuffer("");
HttpURLConnection conn = null;
BufferedReader br = null;
InputStream is = null;
InputStreamReader isr = null;
try {
URL url = new URL(postbody.get("url"));
conn = (HttpURLConnection) url.openConnection();
conn.setConnectTimeout(TIMEOUT_MILLIS);
conn.setReadTimeout(TIMEOUT_MILLIS);
conn.setRequestMethod("GET");// HTTPメソッド
conn.setUseCaches(false);// キャッシュ利用
conn.setDoOutput(false);// リクエストのボディの送信を許可(GETのときはfalse,POSTのときはtrueにする)
conn.setDoInput(true);// レスポンスのボディの受信を許可
// HTTPボディをセット
for (String key : postbody.keySet()) {
if (key != "url"){
conn.setRequestProperty(key, postbody.get(key));// HTTPヘッダをセット
Log.d("conn.setRequestProperty", key + "=" + postbody.get(key));
}
}
conn.connect();
int responseCode = conn.getResponseCode();
if (responseCode == HttpURLConnection.HTTP_OK) {
is = conn.getInputStream();
isr = new InputStreamReader(is, "UTF-8");
br = new BufferedReader(isr);
String line = null;
while ((line = br.readLine()) != null) {
sb.append(line);
}
} else {
// If responseCode is not HTTP_OK
}
} catch (IOException e) {
throw e;
} finally {
if (br != null) {
try {
br.close();
} catch (IOException e) {
}
}
if (isr != null) {
try {
isr.close();
} catch (IOException e) {
}
}
if (is != null) {
try {
is.close();
} catch (IOException e) {
}
}
if (conn != null) {
conn.disconnect();
}
}
return sb.toString();
}
Map<String, String> call() の中で、this.getPost(this._postbody) の戻り値の文字列をJSONオブジェクトにします。
そのオブジェクトの中からキー「PDF」で PDFファイルをBase64でエンコードしたデータを取得します。
そのデータをBase64.decode(json, Base64.DEFAULT) でデコード(復元)されたバイト列を作成します。
そのバイト列がファイル本体になりますからストリームで名前を付けて書き出し保存します。
成功したらファイル名、失敗したらエラー理由を含むハッシュテーブルを Future に返します。
public Map<String, String> call() {
String filename = "";
String err = "";
try {
// 通信でPDFを受け取る処理を記述
// requestを送信
String res = this.getPost(this._postbody);
Log.d("requestPdfTask1",res);
// jsonを受け取り
res = "{\"name\":\"John\",\"PDF\":\"6162632082a082a282a4\"}";
JSONObject jsonObject = new JSONObject(res);
// PDF部分をバイト配列に変換
String json = jsonObject.getString("PDF");
// UTF-8のBase64文字列からbyte[]を復元
byte[] decode = Base64.decode(json, Base64.DEFAULT);
Log.d("requestPdfTask2","jsonObject.getString: " + json);
Log.d("requestPdfTask2","jsonObject.getString: " + decode.toString());
// PDF部分がasciiコードの配列の場合
//JSONArray jsonarray = jsonObject.getJSONArray("PDF");
//int len = jsonarray.length();
//byte[] data = new byte[len];
//for (int i = 0; i < len; i++) {
// data[i] = Integer.valueOf(jsonarray.getString(i)).byteValue();
//}
// ファイルを保存
// この保存先 /storage/emulated/0/Android/data/org.sibainu.relax.room.last1/files
File path = getExternalFilesDir(null);
path.mkdirs();
filename = getPicFileName();
File file = new File(path, filename);
FileOutputStream fs = new FileOutputStream(file);
BufferedOutputStream bos = new BufferedOutputStream(fs);
bos.write(decode);
bos.flush();
bos.close();
} catch (JSONException e) {
Log.e("requestPDF","JSONException: " +e.getMessage());
err = "JSONException: " +e.getMessage();
} catch (Exception e) {
Log.e("requestPDF", "Exception: " + e.getMessage());
err = "Exception: " + e.getMessage();
}
Map<String, String> resmap = new HashMap<>();
resmap.put("filename",filename);
resmap.put("err",err);
return resmap;
}
Future に返されたハッシュテーブルから err の空の判定をして次の処理を行います。
err が空の場合、 new ImagePrint(this._ipaddress, this._filename) でインスタンスを作成してプリントします。
public void requestPdf() {
//クラス requestPostTask の実体を作成
this._backgroundReceiver = new requestPDF.requestPdfTask(this._postbody);
//executorService を作成
ExecutorService executorService = Executors.newSingleThreadExecutor();
//executorService.submit の引数に JsonPostHttp の実体を渡します
//バックグラウンドで非同期処理が行われ戻り値が future に格納されます
Future<Map<String,String>> future;
future = executorService.submit(this._backgroundReceiver);
try {
//future から戻り値を取り出します
String err = future.get().get("err");
if (err != "") {
TextView tv;
tv = findViewById(R.id.tvText1);
tv.setText(err);
} else {
this._filename = future.get().get("filename");
this._imageprint = new ImagePrint(this._ipaddress,
this._filename);
this._imageprint.imagePrint();
}
} catch (ExecutionException | InterruptedException ex) {
Log.w("printerSearch catch", "非同期処理結果の取得で例外発生: ", ex);
} finally {
Log.d("printerSearch finally", "非同期処理完了");
executorService.shutdown();
}
}
private class requestPDF { private String _ipaddress; private Map<String,String> _postbody; private ImagePrint _imageprint; private String _filename; private requestPDF.requestPdfTask _backgroundReceiver; public requestPDF(String ipaddress, Map<String, String> postbody) { this._ipaddress = ipaddress; this._postbody = postbody; } @UiThread public void requestPdf() { //クラス requestPostTask の実体を作成 this._backgroundReceiver = new requestPDF.requestPdfTask(this._postbody); //executorService を作成 ExecutorService executorService = Executors.newSingleThreadExecutor(); //executorService.submit の引数に JsonPostHttp の実体を渡します //バックグラウンドで非同期処理が行われ戻り値が future に格納されます Future<Map<String,String>> future; future = executorService.submit(this._backgroundReceiver); try { //future から戻り値を取り出します String err = future.get().get("err"); if (err != "") { TextView tv; tv = findViewById(R.id.tvText1); tv.setText(err); } else { this._filename = future.get().get("filename"); this._imageprint = new ImagePrint(this._ipaddress, this._filename); this._imageprint.imagePrint(); } } catch (ExecutionException | InterruptedException ex) { Log.w("printerSearch catch", "非同期処理結果の取得で例外発生: ", ex); } finally { Log.d("printerSearch finally", "非同期処理完了"); executorService.shutdown(); } } // 非同期 public class requestPdfTask implements Callable<Map<String,String>> { private Map<String, String> _postbody; //constructor です public requestPdfTask(Map<String,String> postbody) { this._postbody = postbody; } // 非同期 @WorkerThread @Override public Map<String, String> call() { String filename = ""; String err = ""; try { // 通信でPDFを受け取る処理を記述 // requestを送信 String res = this.getPost(this._postbody); // jsonを受け取りJSONオブジェクトを作成 JSONObject jsonObject = new JSONObject(res); // PDF部分をバイト配列に変換 String json = jsonObject.getString("PDF"); // UTF-8のBase64文字列からbyte[]を復元 byte[] decode = Base64.decode(json, Base64.DEFAULT); // PDF部分がasciiコードの配列の場合 //JSONArray jsonarray = jsonObject.getJSONArray("PDF"); //int len = jsonarray.length(); //byte[] data = new byte[len]; //for (int i = 0; i < len; i++) { // data[i] = Integer.valueOf(jsonarray.getString(i)).byteValue(); //} // ファイルを保存 // この保存先 /storage/emulated/0/Android/data/org.sibainu.relax.room.last1/files File path = getExternalFilesDir(null); path.mkdirs(); filename = getPicFileName(); File file = new File(path, filename); FileOutputStream fs = new FileOutputStream(file); BufferedOutputStream bos = new BufferedOutputStream(fs); bos.write(decode); bos.flush(); bos.close(); } catch (JSONException e) { Log.e("requestPDF","JSONException: " +e.getMessage()); err = "JSONException: " +e.getMessage(); } catch (Exception e) { Log.e("requestPDF", "Exception: " + e.getMessage()); err = "Exception: " + e.getMessage(); } Map<String, String> resmap = new HashMap<>(); resmap.put("filename",filename); resmap.put("err",err); return resmap; } private String getPost(Map<String, String> postbody) throws IOException { int TIMEOUT_MILLIS = 10000; StringBuffer sb = new StringBuffer(""); HttpURLConnection conn = null; BufferedReader br = null; InputStream is = null; InputStreamReader isr = null; try { URL url = new URL(postbody.get("url")); conn = (HttpURLConnection) url.openConnection(); conn.setConnectTimeout(TIMEOUT_MILLIS); conn.setReadTimeout(TIMEOUT_MILLIS); conn.setRequestMethod("GET");// HTTPメソッド conn.setUseCaches(false);// キャッシュ利用 conn.setDoOutput(false);// リクエストのボディの送信を許可(GETのときはfalse,POSTのときはtrueにする) conn.setDoInput(true);// レスポンスのボディの受信を許可 // HTTPヘッダをセット for (String key : postbody.keySet()) { if (key != "url"){ conn.setRequestProperty(key, postbody.get(key));// HTTPヘッダをセット Log.d("conn.setRequestProperty", key + "=" + postbody.get(key)); } } conn.connect(); final int responseCode = conn.getResponseCode(); if (responseCode == HttpURLConnection.HTTP_OK) { is = conn.getInputStream(); isr = new InputStreamReader(is, "UTF-8"); br = new BufferedReader(isr); String line = null; while ((line = br.readLine()) != null) { sb.append(line); } } else { // If responseCode is not HTTP_OK } } catch (IOException e) { throw e; } finally { if (br != null) { try { br.close(); } catch (IOException e) { } } if (isr != null) { try { isr.close(); } catch (IOException e) { } } if (is != null) { try { is.close(); } catch (IOException e) { } } if (conn != null) { conn.disconnect(); } } return sb.toString(); } protected String getPicFileName() { Calendar c = Calendar.getInstance(); String s = c.get(Calendar.YEAR) + "_" + (c.get(Calendar.MONTH) + 1) + "_" + c.get(Calendar.DAY_OF_MONTH) + "_" + c.get(Calendar.HOUR_OF_DAY) + "_" + c.get(Calendar.MINUTE) + "_" + c.get(Calendar.SECOND) + ".pdf"; return s; } } } private class requestPDF { private String _ipaddress; private Map<String,String> _postbody; private ImagePrint _imageprint; private String _filename; private requestPDF.requestPdfTask _backgroundReceiver; public requestPDF(String ipaddress, Map<String, String> postbody) { this._ipaddress = ipaddress; this._postbody = postbody; } @UiThread public void requestPdf() { //クラス requestPostTask の実体を作成 this._backgroundReceiver = new requestPDF.requestPdfTask(this._postbody); //executorService を作成 ExecutorService executorService = Executors.newSingleThreadExecutor(); //executorService.submit の引数に JsonPostHttp の実体を渡します //バックグラウンドで非同期処理が行われ戻り値が future に格納されます Future<Map<String,String>> future; future = executorService.submit(this._backgroundReceiver); try { //future から戻り値を取り出します String err = future.get().get("err"); TextView tv; tv = findViewById(R.id.tvText1); tv.setText(err); this._filename = future.get().get("filename"); this._imageprint = new ImagePrint(this._ipaddress, this._filename); this._imageprint.imagePrint(); } catch (ExecutionException | InterruptedException ex) { Log.w("printerSearch catch", "非同期処理結果の取得で例外発生: ", ex); } finally { Log.d("printerSearch finally", "非同期処理完了"); executorService.shutdown(); } } // 非同期 public class requestPdfTask implements Callable<Map<String,String>> { private Map<String, String> _postbody; //constructor です public requestPdfTask(Map<String,String> postbody) { this._postbody = postbody; } // 非同期 @WorkerThread @Override public Map<String, String> call() { String filename = ""; String err = ""; try { // 通信でPDFを受け取る処理を記述 // requestを送信 String res = getPost(_postbody); Log.d("requestPdfTask1",res); // jsonを受け取り res = "{\"name\":\"John\",\"PDF\":\"6162632082a082a282a4\"}"; JSONObject jsonObject = new JSONObject(res); // PDF部分をバイト配列に変換 String json = jsonObject.getString("PDF"); // UTF-8のBase64文字列からbyte[]を復元 byte[] decode = Base64.decode(json, Base64.DEFAULT); Log.d("requestPdfTask2","jsonObject.getString: " + json); Log.d("requestPdfTask2","jsonObject.getString: " + decode.toString()); // PDF部分がasciiコードの配列の場合 //JSONArray jsonarray = jsonObject.getJSONArray("PDF"); //int len = jsonarray.length(); //byte[] data = new byte[len]; //for (int i = 0; i < len; i++) { // data[i] = Integer.valueOf(jsonarray.getString(i)).byteValue(); //} // ファイルを保存 // この保存先 /storage/emulated/0/Android/data/org.sibainu.relax.room.last1/files File path = getExternalFilesDir(null); path.mkdirs(); filename = getPicFileName(); File file = new File(path, filename); FileOutputStream fs = new FileOutputStream(file); BufferedOutputStream bos = new BufferedOutputStream(fs); bos.write(decode); bos.flush(); bos.close(); } catch (JSONException e) { Log.e("requestPDF","JSONException: " +e.getMessage()); err = "JSONException: " +e.getMessage(); } catch (Exception e) { Log.e("requestPDF", "Exception: " + e.getMessage()); err = "Exception: " + e.getMessage(); } Map<String, String> resmap = new HashMap<>(); resmap.put("filename",filename); resmap.put("err",err); return resmap; } private String getPost(Map<String, String> postbody) throws IOException { int TIMEOUT_MILLIS = 10000; StringBuffer sb = new StringBuffer(""); HttpURLConnection conn = null; BufferedReader br = null; InputStream is = null; InputStreamReader isr = null; try { URL url = new URL(postbody.get("url")); conn = (HttpURLConnection) url.openConnection(); conn.setConnectTimeout(TIMEOUT_MILLIS); conn.setReadTimeout(TIMEOUT_MILLIS); conn.setRequestMethod("GET");// HTTPメソッド conn.setUseCaches(false);// キャッシュ利用 conn.setDoOutput(false);// リクエストのボディの送信を許可(GETのときはfalse,POSTのときはtrueにする) conn.setDoInput(true);// レスポンスのボディの受信を許可 // HTTPボディをセット for (String key : postbody.keySet()) { if (key != "url"){ conn.setRequestProperty(key, postbody.get(key));// HTTPヘッダをセット Log.d("conn.setRequestProperty", key + "=" + postbody.get(key)); } } conn.connect(); int responseCode = conn.getResponseCode(); if (responseCode == HttpURLConnection.HTTP_OK) { is = conn.getInputStream(); isr = new InputStreamReader(is, "UTF-8"); br = new BufferedReader(isr); String line = null; while ((line = br.readLine()) != null) { sb.append(line); } } else { // If responseCode is not HTTP_OK } } catch (IOException e) { throw e; } finally { if (br != null) { try { br.close(); } catch (IOException e) { } } if (isr != null) { try { isr.close(); } catch (IOException e) { } } if (is != null) { try { is.close(); } catch (IOException e) { } } if (conn != null) { conn.disconnect(); } } return sb.toString(); } protected String getPicFileName() { Calendar c = Calendar.getInstance(); String s = c.get(Calendar.YEAR) + "_" + (c.get(Calendar.MONTH) + 1) + "_" + c.get(Calendar.DAY_OF_MONTH) + "_" + c.get(Calendar.HOUR_OF_DAY) + "_" + c.get(Calendar.MINUTE) + "_" + c.get(Calendar.SECOND) + ".pdf"; return s; } } }
MainActivity.java 全コード
全体的に直していますので、全コードを記録します。
当該ブログは私のメモ帳ですので見苦しいと思われるかもしてませんがお許しください。
package org.sibainu.relax.room.last1; import androidx.annotation.UiThread; import androidx.annotation.WorkerThread; import androidx.appcompat.app.AppCompatActivity; import android.content.Context; import android.content.SharedPreferences; import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Matrix; import android.graphics.Paint; import android.graphics.Rect; import android.graphics.Typeface; import android.graphics.pdf.PdfDocument; import android.graphics.pdf.PdfRenderer; import android.os.Bundle; import android.os.ParcelFileDescriptor; import android.util.Base64; import android.util.Log; import android.view.View; import android.widget.Button; import android.widget.EditText; import android.widget.TextView; import com.brother.sdk.lmprinter.Channel; import com.brother.sdk.lmprinter.NetworkSearchOption; import com.brother.sdk.lmprinter.OpenChannelError; import com.brother.sdk.lmprinter.PrintError; import com.brother.sdk.lmprinter.PrinterDriver; import com.brother.sdk.lmprinter.PrinterDriverGenerateResult; import com.brother.sdk.lmprinter.PrinterDriverGenerator; import com.brother.sdk.lmprinter.PrinterModel; import com.brother.sdk.lmprinter.PrinterSearchResult; import com.brother.sdk.lmprinter.PrinterSearcher; import com.brother.sdk.lmprinter.setting.QLPrintSettings; import java.io.BufferedOutputStream; import java.io.BufferedReader; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.net.HttpURLConnection; import java.net.URL; import java.net.URLEncoder; import java.util.ArrayList; import java.util.Calendar; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.function.Consumer; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.io.FileNotFoundException; import android.widget.ImageView; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import javax.net.ssl.HttpsURLConnection; public class MainActivity extends AppCompatActivity { private infoPost infopost; private basicData bD; private Map<String, Object> connparams; private TextView tvTellop1; private TextView tvTellop2; private TextView tvTellop3; private EditText etInput1; private EditText etInput2; private EditText etInput3; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); connparams = new HashMap<>(); Map<String, String> params = new HashMap<>(); params.put("url","https://www.sibainu.org/"); params.put("user","user"); params.put("id","id"); params.put("key1","key1"); params.put("key2","key2"); params.put("key3","key3"); connparams.put("post", params); params = new HashMap<>(); params.put("url","https://www.sibainu.org/"); params.put("user","user"); params.put("id","id"); params.put("key1","key1"); params.put("key2","key2"); params.put("key3","key3"); connparams.put("get", params); params = new HashMap<>(); params.put("url","https://www.sibainu.org/"); params.put("user","user"); params.put("id","id"); params.put("key1","key1"); params.put("key2","key2"); params.put("key3","key3"); connparams.put("info", params); infopost = new infoPost((Map<String, String>) connparams.get("info")); infopost.add("onCreate_begin"); ClickListener cl = new ClickListener(); TextView tv; tv = findViewById(R.id.textView01); tv.setOnClickListener(cl); tv = findViewById(R.id.textView02); tv.setOnClickListener(cl); tv = findViewById(R.id.textView03); tv.setOnClickListener(cl); tvTellop1 = findViewById(R.id.tvTellop1); tvTellop2 = findViewById(R.id.tvTellop2); tvTellop3 = findViewById(R.id.tvTellop3); etInput1 = findViewById(R.id.etInput1); etInput2 = findViewById(R.id.etInput2); etInput3 = findViewById(R.id.etInput3); ImageView im = findViewById(R.id.image); im.setBackgroundResource(R.drawable.border); Button btn; btn = findViewById(R.id.button); btn.setOnClickListener(cl); Log.d("onCreate1", "cl"); btn = findViewById(R.id.button2); btn.setOnClickListener(cl); Log.d("onCreate2", "cl"); bD = new basicData("printerIpAddress"); infopost.add("onCreate_end"); } @Override protected void onDestroy() { super.onDestroy(); infopost.add("onDestroy").postinfo(); } @Override protected void onResume() { super.onResume(); //ダミーデータ bD.put("プリンタ名_0", "model00"); bD.put("プリンタ名_1", "model11"); bD.put("プリンタ名_2", "model22"); bD.put("IPアドレス_0", "111.222.1.1"); bD.put("IPアドレス_1", "111.222.2.2"); bD.put("IPアドレス_2", "111.222.3.3"); bD.put("プリンタ数", "3"); String temp = bD.toJsonString(); TextView tvv; tvv = findViewById(R.id.tvText1); tvv.setText(temp); Log.d("onResume1", temp); bD.clear(); int printerCount = Integer.valueOf(bD.get("プリンタ数")); if (printerCount < 1) { // プリンタの検索 PrinterSearch printersearch; printersearch = new PrinterSearch(this); printersearch.printerSearch(); } else { // TextView[] tvlist = {findViewById(R.id.textView01), findViewById(R.id.textView02), findViewById(R.id.textView03)}; int i; for (i = 0; i < tvlist.length; i++) { if (i < printerCount) { tvlist[i].setText("プリンタ名: " + bD.get("プリンタ名_" + i) + "\n" + "IPアドレス: " + bD.get("IPアドレス_" + i)); } else { tvlist[i].setText(""); } } } } private class ClickListener implements View.OnClickListener { @Override public void onClick(View view) { // クリックした TextView の値を取得 TextView tv; int objid = view.getId(); if (objid == R.id.textView01 || objid == R.id.textView02 || objid == R.id.textView03) { tv = findViewById(objid); String str = tv.getText().toString(); //表示先の初期化 tv = findViewById(R.id.tvText1); tv.setText(""); //表示先の初期化 tv = findViewById(R.id.textView04); tv.setText(""); // 正規表現でマッチを確認 Pattern p = Pattern.compile("^[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}$"); Matcher m = p.matcher(str); if (m.find()) { // IPアドレスの表示 tv.setText(str); // プリントの実行 ImagePrint imageprint; imageprint = new ImagePrint("", ""); imageprint.setIpAddress(str); imageprint.setFileName("mypdffile.pdf"); imageprint.imagePrint(); } } else if (objid == R.id.button) { Log.d("generatePdf", "onClock"); pdfFactory pdffactory = new pdfFactory(); pdffactory.generatePdf(); } else if (objid == R.id.button2) { Log.d("requestPdf1", "onClock"); tv = findViewById(R.id.textView04); String ipaddress = tv.getText().toString(); Log.d("requestPdf2", ipaddress); requestPDF requestpdf = new requestPDF(ipaddress, (Map<String, String>) connparams.get("get")); requestpdf.requestPdf(); } } } private class PrinterSearch { private printerSearchTask _backgroundReceiver; private Context _context; public PrinterSearch(Context context) { this._context = context; } @UiThread public void printerSearch() { //クラス printerSearchTask の実体を作成 this._backgroundReceiver = new printerSearchTask(_context); //executorService を作成 ExecutorService executorService = Executors.newSingleThreadExecutor(); //executorService.submit の引数に実体を渡します //バックグラウンドで非同期処理が行われ戻り値が future に格納されます Future<PrinterSearchResult> future; future = executorService.submit(this._backgroundReceiver); PrinterSearchResult channels; try { //future から channels の配列を取り出します channels = future.get(); if (channels != null) { TextView[] tvlist = {findViewById(R.id.textView01), findViewById(R.id.textView02), findViewById(R.id.textView03)}; String modelname = ""; String ipaddress = ""; int i = 0; String[] printerList = new String[channels.getChannels().size()]; bD.clear(); for (Channel channel : channels.getChannels()) { modelname = channel.getExtraInfo().get(Channel.ExtraInfoKey.ModelName); ipaddress = channel.getChannelInfo(); printerList[i] = "プリンタ名: " + modelname + "\n" + "IPアドレス: " + ipaddress; bD.put("プリンタ名_" + i, modelname); bD.put("IPアドレス_" + i, ipaddress); if (i < tvlist.length) { tvlist[i].setText(printerList[i]); } i += 1; } bD.put("プリンタ数", String.valueOf(i)); TextView tv; tv = findViewById(R.id.tvText1); if (printerList.length > 0) { tv.setText(String.join("\n", printerList)); } else { tv.setText("使用できるプリンタはありません"); } } } catch (ExecutionException | InterruptedException ex) { Log.w("printerSearch catch", "非同期処理結果の取得で例外発生: ", ex); } finally { Log.d("printerSearch finally", "非同期処理完了"); executorService.shutdown(); } } private class printerSearchTask implements Callable<PrinterSearchResult> { private PrinterSearchResult _channels; private Context _context; //constructor です public printerSearchTask(Context context) { this._context = context; Log.d("constructor", "printerSearchTask constructor"); } // 非同期 @WorkerThread @Override public PrinterSearchResult call() { try { NetworkSearchOption option = new NetworkSearchOption(5, false); this._channels = PrinterSearcher.startNetworkSearch(this._context, option, new Consumer<Channel>() { @Override public void accept(Channel channel) { } }); } catch (Exception e) { Log.d("Error call catch", "PrinterSearchResult"); e.printStackTrace(); } return this._channels; } } } private class infoPost { private List<String> _info; private Map<String, String> _postbody; public infoPost(Map<String,String> postbody) { this._postbody = postbody; this._info = new ArrayList<>(); } public infoPost add(String addstr) { this._info.add(addstr); return this; } @UiThread public void postinfo() { // List を配列に変換 String[] ss = new String[this._info.size()]; this._info.toArray(ss); // postbody に info データを追加 String data; data = String.join("\n", ss); this._postbody.put("data", data); // 送信データを作成します。 StringBuilder postData = new StringBuilder(); // 連想配列をペアで処理 try { for (Map.Entry<String, String> s : this._postbody.entrySet()) { // 先頭が & にならないようにします。 if (postData.length() != 0) postData.append('&'); // エンコードして結合します。 postData.append(URLEncoder.encode(s.getKey(), "UTF-8")) .append('=') .append(URLEncoder.encode(s.getValue(), "UTF-8")); } requestPost(this._postbody.get("url"), postData.toString()); } catch (Exception e) { e.printStackTrace(); } } @UiThread private void requestPost(String urlstr, String postbody) { //クラス requestPostTask の実体を作成 requestPostTask backgroundReceiver = new requestPostTask(urlstr, postbody); //executorService を作成 ExecutorService executorService = Executors.newSingleThreadExecutor(); //executorService.submit の引数に JsonPostHttp の実体を渡します //バックグラウンドで非同期処理が行われ戻り値が future に格納されます Future<String> future; future = executorService.submit(backgroundReceiver); String result = ""; try { //future から戻り値を取り出します result = future.get(); // テキストビューに表示 TextView tv; tv = findViewById(R.id.tvText1); tv.setText(result); } catch (ExecutionException | InterruptedException ex) { Log.w("printerSearch catch", "非同期処理結果の取得で例外発生: ", ex); } finally { Log.d("printerSearch finally", "非同期処理完了"); executorService.shutdown(); } } public class requestPostTask implements Callable<String> { String _url; String _postbody; //constructor です public requestPostTask(String urlstr, String postbody) { this._url = urlstr; this._postbody = postbody; Log.d("constructor", "requestPostTask constructor"); } // 非同期 @WorkerThread @Override public String call() { HttpURLConnection conn = null; BufferedReader br = null; InputStream is = null; InputStreamReader isr = null; String response = ""; try { URL url = new URL(this._url);// URLを設定します。 conn = (HttpURLConnection) url.openConnection(); conn.setRequestMethod("POST");// HTTPメソッド conn.setDoOutput(true); conn.setConnectTimeout(10000); // 送信するデータをバイト配列に変換 byte[] postDataBytes = this._postbody.toString().getBytes("UTF-8"); conn.getOutputStream().write(postDataBytes);// 送信 int responseCode = conn.getResponseCode();// レスポンスを受け取る // 送信の結果 if (responseCode == HttpsURLConnection.HTTP_OK) { String line; is = conn.getInputStream(); isr = new InputStreamReader(is); br = new BufferedReader(isr); while ((line = br.readLine()) != null) { response += line; } br.close(); } else { response = "送信に不具合がありました"; } } catch (Exception e) { response = "送信エラー: " + e.getMessage(); } // future に渡す値 return response; } } } private class ImagePrint { private String _ipaddress; private String _filename; private File _file; private File _dir = getExternalFilesDir(null); imagePrintTask _backgroundReceiver; public ImagePrint() { this._ipaddress = ""; this._filename = ""; } public ImagePrint(String ipaddress, String filename) { this._ipaddress = ipaddress; this._filename = filename; this._file = new File(this._dir, this._filename); } public void setIpAddress(String ipaddress) { this._ipaddress = ipaddress; } public void setFileName(String filename) { this._filename = filename; } @UiThread public void imagePrint() { //クラス requestPostTask の実体を作成 this._backgroundReceiver = new imagePrintTask(this._ipaddress, this._filename); //executorService を作成 ExecutorService executorService = Executors.newSingleThreadExecutor(); //executorService.submit の引数に JsonPostHttp の実体を渡します //バックグラウンドで非同期処理が行われ戻り値が future に格納されます Future<String> future; future = executorService.submit(this._backgroundReceiver); String result = ""; try { //future から戻り値を取り出します result = future.get(); // テキストビューに表示 TextView tv; tv = findViewById(R.id.tvText1); tv.setText(result); Log.d("imagePrint result", result); } catch (ExecutionException | InterruptedException ex) { Log.w("Error imagePrint catch", "非同期処理結果の取得で例外発生: ", ex); } finally { executorService.shutdown(); } } private class imagePrintTask implements Callable<String> { private String _ipaddress; private String _filename; private File _dir = getExternalFilesDir(null); public imagePrintTask(String ipaddress, String filename) { this._ipaddress = ipaddress; this._filename = filename; Log.d("constructor", "imagePrintTask constructor"); } // 非同期 @WorkerThread @Override public String call() { // https://support.brother.com/g/s/es/htmldoc/mobilesdk/guide/getting-started/getting-started-android.html // https://support.brother.com/g/s/es/htmldoc/mobilesdk/guide/print-image.html // https://support.brother.com/g/s/es/htmldoc/mobilesdk/guide/discover-printer.html Channel channel = Channel.newWifiChannel(this._ipaddress); PrinterDriverGenerateResult result = PrinterDriverGenerator.openChannel(channel); if (result.getError().getCode() != OpenChannelError.ErrorCode.NoError) { Log.e("Error OpenChannel", "Error - Open Channel: " + result.getError().getCode()); // future に渡す値 return "Error OpenChannel - IPアドレスのエラー"; } File file = new File(this._dir, this._filename); if (file == null) { // future に渡す値 return "Error File - イメージファイルを取得できません"; } PrinterDriver printerDriver = result.getDriver(); String response = ""; try { // プリンタ設定 QLPrintSettings printSettings = new QLPrintSettings(PrinterModel.QL_820NWB); printSettings.setLabelSize(QLPrintSettings.LabelSize.RollW103); printSettings.setAutoCut(true); printSettings.setWorkPath(this._dir.toString()); PrintError printError = printerDriver.printImage(file.toString(), printSettings); if (printError.getCode() != PrintError.ErrorCode.NoError) { Log.d("Error Print", "Error - Print Image: " + printError.getCode()); response = "Error - Print Image"; } else { Log.d("Success Print", "Success - Print Image"); response = "Success - Print Image"; } } catch (Exception e) { response = "Error Another catch - " + e.toString(); } finally { printerDriver.closeChannel(); } // future に渡す値 return response; } } } private class pdfFactory { File _file; public pdfFactory() { Log.d("pdfFactory", "コンストラクタ0"); } public pdfFactory(File file) { Log.d("pdfFactory", "コンストラクタ1"); this._file = file; } // メッソド public void setFileName(String filename) { File dir = getExternalFilesDir(null); this._file = new File(dir, filename); } public void generatePdf() { Context context = MainActivity.this; // PDFドキュメント生成用のクラスを作成 PdfDocument pdfDocument = new PdfDocument(); Resources r; PdfDocument.Page page; Canvas canvas; Matrix matrix = new Matrix(); Paint text = new Paint(); Paint paint; Rect rect; int pages = 0; r = getResources(); // A4横(72dpi) int a4X = 595; // A4縦(72dpi) int a4Y = 842; // roll1201横29mm(72dpi) int roll1201X = 85; // roll1201縦90mm(72dpi) int roll1201Y = 264; // 指定の縦横幅(ピクセル)でページを作る page = pdfDocument.startPage(new PdfDocument.PageInfo.Builder(roll1201X, roll1201Y, pages).create()); // ページに書き込むためのキャンバスを取得する canvas = page.getCanvas(); // 画像を書き込む // 0.5倍調整 // matrix.postScale(0.5f, 0.5f); // canvas.drawBitmap(Bitmap.createBitmap(bitmap, 0, 0, width, height, matrix, true), // 100, 100, null); // 描画の座標系を左上を中心に右回り90度回転します canvas.rotate(90); // 描画の座標系の原点を右上に移動します canvas.translate(0, -85); paint = new Paint(); paint.setColor(Color.argb(255, 255, 255, 255)); rect = new Rect(0, 0, roll1201Y, roll1201X); canvas.drawRect(rect, paint); // ビットマップを描画します Bitmap bitmap = BitmapFactory.decodeResource(r, R.drawable.sample); int width = bitmap.getWidth(); int height = bitmap.getHeight(); canvas.drawBitmap(bitmap, new Rect(0, 0, width, height), new Rect(0, 0, width / 4, height / 4), null); // 文字を書き込む text.setTypeface(Typeface.create(Typeface.SANS_SERIF, Typeface.BOLD_ITALIC)); text.setColor(context.getColor(R.color.theme500)); text.setTextSize(10); text.setTextAlign(Paint.Align.LEFT); canvas.drawText("テスト!!テスト!!!", 85, 15, text); paint = new Paint(); paint.setTypeface(Typeface.create(Typeface.SANS_SERIF, Typeface.BOLD_ITALIC)); paint.setColor(context.getColor(R.color.theme600)); paint.setTextSize(8); paint.setTextAlign(Paint.Align.LEFT); String buf1 = tvTellop1.getText().toString() + ":" + etInput1.getText().toString(); canvas.drawText(buf1, 10, 30, paint); paint = new Paint(); paint.setTypeface(Typeface.create(Typeface.SANS_SERIF, Typeface.BOLD_ITALIC)); paint.setColor(context.getColor(R.color.theme700)); paint.setTextSize(8); paint.setTextAlign(Paint.Align.LEFT); String buf2 = tvTellop2.getText().toString() + ":" + etInput2.getText().toString(); // 描画の範囲は、x方向は 0 ⇒ roll1201Y y方向は 0 ⇒ roll1201X canvas.drawText(buf2, (roll1201Y - (int) paint.measureText(buf2)) / 2, 55, paint); paint = new Paint(); paint.setTypeface(Typeface.create(Typeface.SANS_SERIF, Typeface.BOLD_ITALIC)); paint.setColor(context.getColor(R.color.theme800)); paint.setTextSize(8); paint.setTextAlign(Paint.Align.LEFT); String buf3 = tvTellop3.getText().toString() + ":" + etInput3.getText().toString(); // 描画の範囲は、x方向は 0 ⇒ roll1201Y y方向は 0 ⇒ roll1201X canvas.drawText(buf3, roll1201Y - (int) paint.measureText(buf3), 80, paint); // ページを閉じる pdfDocument.finishPage(page); try { // ファイルを開いてPDFを書き込む // 次の指定は不可 File path = Environment.getExternalStoragePublicDirectory( // Environment.DIRECTORY_DOCUMENTS); //この保存先 /storage/emulated/0/Android/data/org.sibainu.relax.room.last1/files File path = getExternalFilesDir(null); Log.d("getExternalFilesDir", path.toString()); path.mkdirs(); _file = new File(path, getPicFileName()); pdfDocument.writeTo(new FileOutputStream(_file)); // PDF の描画 drawPDF(); } catch (IOException e) { e.printStackTrace(); } pdfDocument.close(); } protected String getPicFileName() { Calendar c = Calendar.getInstance(); String s = c.get(Calendar.YEAR) + "_" + (c.get(Calendar.MONTH) + 1) + "_" + c.get(Calendar.DAY_OF_MONTH) + "_" + c.get(Calendar.HOUR_OF_DAY) + "_" + c.get(Calendar.MINUTE) + "_" + c.get(Calendar.SECOND) + ".pdf"; return s; } // メソッド public void drawPDF() throws RuntimeException { if (_file == null) { return; } try { // pdfを読み込み、1ページ目を取得 ParcelFileDescriptor fd = ParcelFileDescriptor.open(_file, ParcelFileDescriptor.MODE_READ_ONLY); PdfRenderer renderer = new PdfRenderer(fd); PdfRenderer.Page page = renderer.openPage(0); try { ImageView view = findViewById(R.id.image); // Bitmap の270度回転しますので描画の縦横を交換します int drawHeight = view.getWidth(); int drawWidth = view.getHeight(); Log.d("test", "drawWidth=" + drawWidth + ", drawHeight=" + drawHeight); float pdfWidth = page.getWidth(); float pdfHeight = page.getHeight(); Log.d("test", "pdfWidth=" + pdfWidth + ", pdfHeight=" + pdfHeight); // PDFのサイズを基本にした縦横比を計算 float wRatio = drawWidth / pdfWidth; float hRatio = drawHeight / pdfHeight; Log.d("test", "wRatio=" + wRatio + ", hRatio=" + hRatio); // Bitmap の最大サイズを計算します if (wRatio <= hRatio) { drawHeight = (int) Math.ceil(pdfHeight * wRatio); } else { drawWidth = (int) Math.ceil(pdfWidth * hRatio); } Log.d("test", "drawWidth=" + drawWidth + ", drawHeight=" + drawHeight); // Bitmap生成して描画 Bitmap bitmap = Bitmap.createBitmap(drawWidth, drawHeight, Bitmap.Config.ARGB_8888); page.render(bitmap, new Rect(0, 0, drawWidth, drawHeight), null, PdfRenderer.Page.RENDER_MODE_FOR_DISPLAY); view.setImageBitmap(rotateBitmap(bitmap, 270)); } catch (Exception e) { e.printStackTrace(); } finally { try { if (fd != null) { fd.close(); } } catch (IOException e) { e.printStackTrace(); } if (page != null) { page.close(); } if (renderer != null) { renderer.close(); } } } catch (FileNotFoundException e) { Log.d("drawPDF", "ファイルが開けられません"); } catch (IOException e) { throw new RuntimeException(e); } } private Bitmap rotateBitmap(Bitmap targetBmp, int degrees) { int w = targetBmp.getWidth(); int h = targetBmp.getHeight(); Matrix mat = new Matrix(); mat.setRotate(degrees); Bitmap responseBmp = Bitmap.createBitmap(targetBmp, 0, 0, w, h, mat, false); return responseBmp; } } private class requestPDF { private String _ipaddress; private Map<String,String> _postbody; private ImagePrint _imageprint; private String _filename; private requestPDF.requestPdfTask _backgroundReceiver; public requestPDF(String ipaddress, Map<String, String> postbody) { this._ipaddress = ipaddress; this._postbody = postbody; } @UiThread public void requestPdf() { //クラス requestPostTask の実体を作成 this._backgroundReceiver = new requestPDF.requestPdfTask(this._postbody); //executorService を作成 ExecutorService executorService = Executors.newSingleThreadExecutor(); //executorService.submit の引数に JsonPostHttp の実体を渡します //バックグラウンドで非同期処理が行われ戻り値が future に格納されます Future<Map<String,String>> future; future = executorService.submit(this._backgroundReceiver); try { //future から戻り値を取り出します String err = future.get().get("err"); if (err != "") { TextView tv; tv = findViewById(R.id.tvText1); tv.setText(err); } else { this._filename = future.get().get("filename"); this._imageprint = new ImagePrint(this._ipaddress, this._filename); this._imageprint.imagePrint(); } } catch (ExecutionException | InterruptedException ex) { Log.w("printerSearch catch", "非同期処理結果の取得で例外発生: ", ex); } finally { Log.d("printerSearch finally", "非同期処理完了"); executorService.shutdown(); } } // 非同期 public class requestPdfTask implements Callable<Map<String,String>> { private Map<String, String> _postbody; //constructor です public requestPdfTask(Map<String,String> postbody) { this._postbody = postbody; } // 非同期 @WorkerThread @Override public Map<String, String> call() { String filename = ""; String err = ""; try { // 通信でPDFを受け取る処理を記述 // requestを送信 String res = this.getPost(this._postbody); Log.d("requestPdfTask1",res); // jsonを受け取り res = "{\"name\":\"John\",\"PDF\":\"6162632082a082a282a4\"}"; JSONObject jsonObject = new JSONObject(res); // PDF部分をバイト配列に変換 String json = jsonObject.getString("PDF"); // UTF-8のBase64文字列からbyte[]を復元 byte[] decode = Base64.decode(json, Base64.DEFAULT); Log.d("requestPdfTask2","jsonObject.getString: " + json); Log.d("requestPdfTask2","jsonObject.getString: " + decode.toString()); // PDF部分がasciiコードの配列の場合 //JSONArray jsonarray = jsonObject.getJSONArray("PDF"); //int len = jsonarray.length(); //byte[] data = new byte[len]; //for (int i = 0; i < len; i++) { // data[i] = Integer.valueOf(jsonarray.getString(i)).byteValue(); //} // ファイルを保存 // この保存先 /storage/emulated/0/Android/data/org.sibainu.relax.room.last1/files File path = getExternalFilesDir(null); path.mkdirs(); filename = getPicFileName(); File file = new File(path, filename); FileOutputStream fs = new FileOutputStream(file); BufferedOutputStream bos = new BufferedOutputStream(fs); bos.write(decode); bos.flush(); bos.close(); } catch (JSONException e) { Log.e("requestPDF","JSONException: " +e.getMessage()); err = "JSONException: " +e.getMessage(); } catch (Exception e) { Log.e("requestPDF", "Exception: " + e.getMessage()); err = "Exception: " + e.getMessage(); } Map<String, String> resmap = new HashMap<>(); resmap.put("filename",filename); resmap.put("err",err); return resmap; } private String getPost(Map<String, String> postbody) throws IOException { int TIMEOUT_MILLIS = 10000;// タイムアウトミリ秒:0は無限 StringBuffer sb = new StringBuffer(""); HttpURLConnection conn = null; BufferedReader br = null; InputStream is = null; InputStreamReader isr = null; try { URL url = new URL(postbody.get("url")); conn = (HttpURLConnection) url.openConnection(); conn.setConnectTimeout(TIMEOUT_MILLIS); conn.setReadTimeout(TIMEOUT_MILLIS); conn.setRequestMethod("GET");// HTTPメソッド conn.setRequestProperty("Accept", "application/json"); conn.setUseCaches(false);// キャッシュ利用 conn.setDoOutput(false);// リクエストのボディの送信を許可(GETのときはfalse,POSTのときはtrueにする) conn.setDoInput(true);// レスポンスのボディの受信を許可 // HTTPヘッダをセット for (String key : postbody.keySet()) { if (key != "url"){ conn.setRequestProperty(key, postbody.get(key));// HTTPヘッダをセット Log.d("conn.setRequestProperty", key + "=" + postbody.get(key)); } } conn.connect(); int responseCode = conn.getResponseCode(); if (responseCode == HttpURLConnection.HTTP_OK) { is = conn.getInputStream(); isr = new InputStreamReader(is, "UTF-8"); br = new BufferedReader(isr); String line = null; while ((line = br.readLine()) != null) { sb.append(line); } } else { // If responseCode is not HTTP_OK } } catch (IOException e) { throw e; } finally { // fortify safeかつJava1.6 compliantなclose処理 if (br != null) { try { br.close(); } catch (IOException e) { } } if (isr != null) { try { isr.close(); } catch (IOException e) { } } if (is != null) { try { is.close(); } catch (IOException e) { } } if (conn != null) { conn.disconnect(); } } return sb.toString(); } protected String getPicFileName() { Calendar c = Calendar.getInstance(); String s = c.get(Calendar.YEAR) + "_" + (c.get(Calendar.MONTH) + 1) + "_" + c.get(Calendar.DAY_OF_MONTH) + "_" + c.get(Calendar.HOUR_OF_DAY) + "_" + c.get(Calendar.MINUTE) + "_" + c.get(Calendar.SECOND) + ".pdf"; return s; } } } private class basicData { SharedPreferences _data; SharedPreferences.Editor _editor; public basicData(String name) { _data = getSharedPreferences(name, Context.MODE_PRIVATE); _editor = _data.edit(); } public void put(String key, String value) { this._editor.putString(key, value); this._editor.apply(); } public String get(String key) { return this._data.getString(key, ""); } public String remove(String key) { String temp = this.get(key); this._editor.remove(key); return temp; } public String clear() { String temp = this.toJsonString(); this._editor.clear(); this.put("プリンタ数","0"); return temp; } public Boolean exists(String key) { return (this._data.getString(key, "nokey") != "nokey"); } public SharedPreferences.Editor editor() { return _editor; } public final String toJsonString() { JSONObject preferencesJson = new JSONObject(); Map<String, ?> map = this._data.getAll(); try { for (Map.Entry<String, ?> entry : map.entrySet()) { preferencesJson.put(entry.getKey(), entry.getValue()); } } catch (JSONException e) { e.printStackTrace(); } return preferencesJson.toString(); } public final String toString(Context context) { return this._data.getAll().toString(); } } }
activity_main.xml 全コード
<?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity"> <TextView android:id="@+id/tvText1" android:layout_width="0dp" android:layout_height="60dp" android:layout_marginStart="8dp" android:text="" app:layout_constraintBottom_toTopOf="@+id/image" app:layout_constraintEnd_toStartOf="@+id/guideline3" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> <androidx.constraintlayout.widget.Guideline android:id="@+id/guideline" android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="horizontal" app:layout_constraintGuide_begin="254dp" /> <androidx.constraintlayout.widget.Guideline android:id="@+id/guideline2" android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="horizontal" app:layout_constraintGuide_begin="0dp" /> <androidx.constraintlayout.widget.Guideline android:id="@+id/guideline3" android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="vertical" app:layout_constraintGuide_percent="0.79" /> <TextView android:id="@+id/textView01" android:layout_width="0dp" android:layout_height="60dp" android:layout_marginTop="8dp" android:layout_marginStart="8dp" android:text="01" android:clickable="true" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="@+id/guideline3" app:layout_constraintTop_toTopOf="@+id/guideline2" /> <TextView android:id="@+id/textView02" android:layout_width="0dp" android:layout_height="60dp" android:layout_marginTop="8dp" android:layout_marginStart="8dp" android:text="02" android:clickable="true" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="1.0" app:layout_constraintStart_toStartOf="@+id/guideline3" app:layout_constraintTop_toBottomOf="@+id/textView01" /> <TextView android:id="@+id/textView03" android:layout_width="0dp" android:layout_height="60dp" android:layout_marginTop="10dp" android:layout_marginStart="8dp" android:text="192.168.1.21" android:clickable="true" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="@+id/guideline3" app:layout_constraintTop_toBottomOf="@+id/textView02" /> <TextView android:id="@+id/textView04" android:layout_width="0dp" android:layout_height="60dp" android:layout_marginStart="8dp" android:text="TextView" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="@+id/guideline3" app:layout_constraintTop_toTopOf="@+id/guideline" /> <TextView android:id="@+id/tvTellop1" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginTop="8dp" android:layout_marginStart="8dp" android:text="@string/tv_tellop1" android:textSize="11sp" app:layout_constraintEnd_toStartOf="@+id/guideline3" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="@+id/guideline" /> <EditText android:id="@+id/etInput1" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginTop="8dp" android:layout_marginStart="8dp" android:inputType="text" android:text="@string/et_input1" android:textSize="11sp" app:layout_constraintEnd_toStartOf="@+id/guideline3" app:layout_constraintHorizontal_bias="0.0" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/tvTellop1" /> <TextView android:id="@+id/tvTellop2" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginTop="15dp" android:layout_marginStart="8dp" android:text="@string/tv_tellop2" android:textSize="11sp" app:layout_constraintEnd_toStartOf="@+id/guideline3" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/etInput1" /> <EditText android:id="@+id/etInput2" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginTop="8dp" android:layout_marginStart="8dp" android:inputType="text" android:text="@string/et_input2" android:textSize="11sp" app:layout_constraintEnd_toStartOf="@+id/guideline3" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/tvTellop2" /> <TextView android:id="@+id/tvTellop3" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginTop="15dp" android:layout_marginStart="8dp" android:text="@string/tv_tellop3" android:textSize="11sp" app:layout_constraintEnd_toStartOf="@+id/guideline3" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/etInput2" /> <EditText android:id="@+id/etInput3" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginTop="8dp" android:layout_marginStart="8dp" android:inputType="text" android:textSize="11sp" android:text="@string/et_input3" app:layout_constraintEnd_toStartOf="@+id/guideline3" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/tvTellop3" /> <androidx.constraintlayout.widget.Guideline android:id="@+id/guideline4" android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="horizontal" app:layout_constraintGuide_percent="1.0" /> <Button android:id="@+id/button" android:layout_width="508dp" android:layout_height="47dp" android:text="Button" app:layout_constraintBottom_toBottomOf="@+id/guideline4" app:layout_constraintStart_toStartOf="parent" /> <Button android:id="@+id/button2" android:layout_width="0dp" android:layout_height="47dp" android:text="Button" app:layout_constraintBottom_toBottomOf="@+id/guideline4" app:layout_constraintEnd_toStartOf="@+id/guideline3" app:layout_constraintHorizontal_bias="1.0" app:layout_constraintStart_toEndOf="@+id/button" /> <ImageView android:id="@+id/image" android:layout_width="0dp" android:layout_height="0dp" app:layout_constraintBottom_toTopOf="@+id/guideline" app:layout_constraintEnd_toStartOf="@+id/guideline3" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/tvText1" /> </androidx.constraintlayout.widget.ConstraintLayout>