Memo

メモ > 技術 > IDE: AndroidStudio > 作例(XMLレイアウト / RSSリーダー)

■作例(XMLレイアウト / RSSリーダー)
以下で新規にプロジェクトを作成 プロジェクトの選択: Empty Activity プロジェクトの名前: reader ビューバインディングを使えるようにする build.gradle を変更したら「Sync Now」をクリック 画像表示のために、Picassoを使えるようにする 同ファイルの dependencies 内の implementation の一覧の最後に以下を追加
implementation 'com.squareup.picasso:picasso:2.71828'
build.gradle を変更したら「Sync Now」をクリック なお、バージョンとして「2.5.2」を指定すると「Sync Now」は表示されなかった https://github.com/square/picasso を参考に、「2.71828」を指定すると表示された Webページ表示のために、Chrome Custom Tabsを使えるようにする 同ファイルの dependencies 内の implementation の一覧の最後に以下を追加
implementation 'androidx.browser:browser:1.0.0'
build.gradle を変更したら「Sync Now」をクリック 今も「androidx.browser:browser:1.0.0」の指定で正しいかどうかは確認しておきたい なお「implementation 'com.android.support:customtabs:27.1.1'」という指定は古い方法だと思われる APK creation failure due to Duplicate class android.support - Stack Overflow https://stackoverflow.com/questions/59239626/apk-creation-failure-due-to-duplicate-class-android-sup... AWS CognitoにAndroidネイティブアプリでサインインする - Qiita https://qiita.com/poruruba/items/12985ec474364594be55 マニフェストファイルを編集し、インターネットに接続できるようにする 追加場所は、ルートであるmanifestの直下でいい manifests/AndroidManifest.xml
<uses-permission android:name="android.permission.INTERNET" />
リスト用のレイアウトを作成する res/layout/list_article_cell.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="wrap_content"> <TextView android:id="@+id/titleView" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginStart="8dp" android:layout_marginTop="4dp" android:layout_marginEnd="8dp" android:layout_marginBottom="4dp" android:textAppearance="@style/TextAppearance.AppCompat.Medium" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toStartOf="@+id/imageView" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" app:layout_constraintVertical_bias="0.08" tools:text="ニュースタイトル" /> <TextView android:id="@+id/nameView" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="8dp" android:layout_marginTop="4dp" android:layout_marginEnd="8dp" android:layout_marginBottom="4dp" android:textAppearance="@style/TextAppearance.AppCompat.Medium" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="0.0" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/titleView" tools:text="名前" /> <ImageView android:id="@+id/imageView" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="8dp" android:layout_marginTop="4dp" android:layout_marginEnd="8dp" android:layout_marginBottom="4dp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="0.98" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" app:srcCompat="@android:drawable/ic_popup_sync" /> </androidx.constraintlayout.widget.ConstraintLayout>
メイン画面のレイアウトを調整する res/layout/lactivity_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"> <androidx.recyclerview.widget.RecyclerView android:id="@+id/articlesView" android:layout_width="0dp" android:layout_height="0dp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" tools:listitem="@layout/list_article_cell" /> </androidx.constraintlayout.widget.ConstraintLayout>
app/java/org.refirio.reader/HttpTask.kt
package org.refirio.reader import android.os.AsyncTask import android.util.Log import java.io.* import java.net.HttpURLConnection import java.net.URL class HttpTask(callback: (String?) -> Unit) : AsyncTask<String, Unit, String>() { var callback = callback override fun doInBackground(vararg params: String): String? { val url = URL(params[1]) val httpClient = url.openConnection() as HttpURLConnection httpClient.setReadTimeout(10 * 1000) httpClient.setConnectTimeout(10 * 1000) httpClient.requestMethod = params[0] if (params[0] == "POST") { httpClient.instanceFollowRedirects = false httpClient.doOutput = true httpClient.doInput = true httpClient.useCaches = false httpClient.setRequestProperty("Content-Type", "application/json; charset=utf-8") } try { if (params[0] == "POST") { httpClient.connect() val os = httpClient.getOutputStream() val writer = BufferedWriter(OutputStreamWriter(os, "UTF-8")) writer.write(params[2]) writer.flush() writer.close() os.close() } if (httpClient.responseCode == HttpURLConnection.HTTP_OK) { val stream = BufferedInputStream(httpClient.inputStream) val data: String = readStream(inputStream = stream) return data } else { Log.d("HttpTask", "ERROR: ${httpClient.responseCode}") } } catch (e: Exception) { e.printStackTrace() } finally { httpClient.disconnect() } return null } fun readStream(inputStream: BufferedInputStream): String { val bufferedReader = BufferedReader(InputStreamReader(inputStream)) val stringBuilder = StringBuilder() bufferedReader.forEachLine { stringBuilder.append(it) } return stringBuilder.toString() } override fun onPostExecute(result: String?) { super.onPostExecute(result) callback(result) } }
app/java/org.refirio.reader/ArticleAcapter.kt
package org.refirio.reader import android.content.Context import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.ImageView import android.widget.TextView import androidx.recyclerview.widget.RecyclerView import com.squareup.picasso.Picasso import org.json.JSONArray import org.json.JSONObject import java.text.ParseException import java.text.SimpleDateFormat class ArticleAcapter( context: Context, private val articles: JSONArray, private val onItemClicked: (JSONObject) -> Unit ) : RecyclerView.Adapter<ArticleAcapter.ViewHolder>() { // レイアウトからViewを生成するInflater private val inflater = LayoutInflater.from(context) override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { // Viewを生成する val view = inflater.inflate(R.layout.list_article_cell, parent, false) // ViewHolderを作る val viewHolder = ViewHolder(view) // Viewをタップしたときの処理 view.setOnClickListener { // アダプター上の家を得る val position = viewHolder.adapterPosition // 位置に応じたデータを得る val article = articles.getJSONObject(position) // コールバックを呼び出す onItemClicked(article) } return viewHolder } override fun getItemCount() = articles.length() override fun onBindViewHolder(holder: ViewHolder, position: Int) { // 位置に応じたデータを得る val article = articles.getJSONObject(position) // 表示内容を更新する holder.title.text = article.getString("title") holder.name.text = "by " + article.getString("name") + " at " + article.getString("datetime").toDateTime("MM/dd HH:mm") Picasso.get() .load(article.getString("image")) // 画像のURL .resize(100, 100) // 表示サイズを指定 .centerCrop() // 中央から切り出し .into(holder.image) // imageViewに流し込み } // Viewへの参照を持っておくViewHolder class ViewHolder(view: View) : RecyclerView.ViewHolder(view) { val title = view.findViewById<TextView>(R.id.titleView) val name = view.findViewById<TextView>(R.id.nameView) val image = view.findViewById<ImageView>(R.id.imageView) } } fun String.toDateTime(pattern: String = "yyyy/MM/dd HH:mm:ss"): String { val dateFormat = SimpleDateFormat("yyyy-MM-dd HH:mm:ss") val datetime = try { dateFormat.parse(this) } catch (e: ParseException) { null } if (datetime == null) { return "" } else { return SimpleDateFormat(pattern).format(datetime) } }
app/java/org.refirio.reader/MainActivity.kt
package org.refirio.reader import android.net.Uri import android.os.Bundle import androidx.appcompat.app.AppCompatActivity import androidx.browser.customtabs.CustomTabsIntent import androidx.recyclerview.widget.LinearLayoutManager import org.json.JSONObject import org.refirio.reader.databinding.ActivityMainBinding class MainActivity : AppCompatActivity() { private lateinit var binding: ActivityMainBinding override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding = ActivityMainBinding.inflate(layoutInflater) val view = binding.root setContentView(view) // JSONを読み込み HttpTask({ if (it == null) { println("Data is empty.") return@HttpTask } // JSONを解析する val json = JSONObject(it) val articles = json.getJSONArray("articles") // RecyclerViewをレイアウトから探す //val recyclerView = findViewById<RecyclerView>(R.id.articles) // RecyclerViewにアダプターをセットする binding.articlesView.adapter = ArticleAcapter(this, articles) { article -> // 記事をタップしたらChrome Custom Tabsで開く val intent = CustomTabsIntent.Builder().build() intent.launchUrl(this, Uri.parse(article.getString("url"))) } // レイアウトマネージャーをセットする binding.articlesView.layoutManager = LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false) }).execute("GET", "https://refirio.org/reader/?type=json") } }

Advertisement