読者です 読者をやめる 読者になる 読者になる

アニメイトラボ開発者ブログ

株式会社アニメイトラボの開発者ブログです


アニメイトラボ開発者ブログ

developer.animatelab.com


Android で一部の文字列をリンク化した TextView を生成するクラスを実装してみました #Kotlin

抱き枕と戯れる人生を謳歌するバトルプログラマー柴田智也 id:bps_tomoya です。

Android アプリケーション開発をしていると、一部の文字列をリンク化したテキストを表示したいという場面があると思います。 私も例に漏れずそういったシーンに遭遇したので、ひょっとするともっと簡単な実装ができるのかもしれませんが、独自のクラスを実装してみました。

このクラスは、

  • こちら(URL A のリンクを設定する)
  • こちら(URL B のリンクを設定する)

のようにリンク化対象文字列が重複してしまった場合にも、それぞれにそれぞれのリンクが設定されることを考慮したクラスになっております。
また基礎的な実装のみに止めているため、メソッドに渡す引数などは状況に合わせて実装する必要があることにご留意ください。

検証環境

  • Android 5.1 (HTC Desire 626)
  • Kotlin 1.0.3

コード(Kotlin)

/**
 * 一部の文字列をリンク化した TextView を生成するクラス。
 * Activity から独立したクラスなので、context を受け取ってインスタンス化されます。
 */
class TextViewWithHyperLinkBuilder(context: Context) {
    private val context: Context by lazy { context }

    /**
     * 文字列の一部をリンク化した TextView を返却する。
     */
    fun getTextViewWithHyperLink(): TextView {
        // 対象文字列。
        val contentText = "サイト1はこちら! サイト2はこちら!"

        // リンク化対象の文字列と URL マップ。
        // キー重複の可能性を受容できるようにキーは整数値を割り当てています。
        // また、この後の処理で順序保証が必要となる観点から TreeMap を利用します。
        val map = TreeMap<Int, Map<String, String>>()
        map.put(0, mapOf("こちら" to "https://www.animatelab.com/"))
        map.put(1, mapOf("こちら" to "http://developer.animatelab.com/"))

        // 文字列の一部をリンク化する処理を呼び出し、返り値を TextView に格納する。
        val spannableString = this.createSpannableString(contentText, map)
        val textView = TextView(this.context)
        textView.text = spannableString
        textView.movementMethod = LinkMovementMethod.getInstance()

        return textView
    }

    /**
     * リンク化するべき文字列の一部の範囲を探索し、タップイベントを設定した文字列を返却する。
     */
    private fun createSpannableString(message: String, map: TreeMap<Int, Map<String, String>>): SpannableString {
        val spannableString = SpannableString(message)
        val prevFindEndHistory = HashMap<String, Int>()

        map.entries.forEach { e ->
            val element = e.value.entries.elementAt(0)
            val pattern = Pattern.compile(element.key)
            // 前回の探索で一致した箇所の end 履歴を取り出します。
            // その値を使って対象文字列を substring したものを pattern.matcher に渡すことで、
            // 前回探索した範囲を探索対象から除外します。
            val prevFindEnd = prevFindEndHistory[element.key] ?: 0
            val matcher = pattern.matcher(message.substring(prevFindEnd))
            var start = 0
            var end = 0

            while (matcher.find()) {
                // 探索一致箇所の start と end は元の対象文字列に対する値としてはズレが発生してしまいます。
                // substring した文字数ぶんだけズレが発生しているのでprevFindEnd を加算します。
                start = matcher.start() + prevFindEnd
                end = matcher.end() + prevFindEnd
                break
            }

            // 今回の探索で一致した箇所の end 履歴を格納します。
            prevFindEndHistory.put(element.key, end)

            // 対象文字列のリンク化するべき範囲にイベントを設定します。
            spannableString.setSpan(object: ClickableSpan() {
                override fun onClick(textView: View) {
                    val url = element.value
                    val uri = Uri.parse(url)
                    val intent = Intent(Intent.ACTION_VIEW, uri)
                    context.startActivity(intent)
                }
            }, start, end, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
        }

        return spannableString
    }
}

生成された TextView を Activity に表示すると、このような結果になります。

f:id:bps_tomoya:20160805133231p:plain:w300

1つめの「こちら」をクリックするとアニメイトラボのサイトが、

f:id:bps_tomoya:20160805132358p:plain:w300

2つめの「こちら」をクリックするとアニメイトラボ開発者ブログが WEB ブラウザで開きます。

f:id:bps_tomoya:20160805132405p:plain:w300

ぜひ参考にしてみてください!

「週末54時間の起業体験」Startup Weekend Ogaki 03にコーチ参加します

古きよき時代から来ました、まじめなSE、まじめにSE。 CTOの id:bash0C7 です。

7/29(金)〜31(日)の3日間行われるスタートアップ体験イベントStartup Weekend Ogaki 03にコーチの一人として参加します。

swogaki.doorkeeper.jp

中部地方に縁のある方はもとより他府県の方もぜひご参加ください。

前回の様子

f:id:bash0C7:20160715013135p:plain *1

Startup Weekend OgakiのFBページでの過去開催の写真を見てイメージと期待を膨らませましょう。

Startup Weekendとは

Webサイトでこのように説明されています。

nposw.org

“スタートアップウィークエンド(SW)”は、金曜夜から日曜夜まで54時間かけて開催される、「スタートアップ体験イベント」です。週末だけで参加者は、アイディアをカタチにするための方法論を学び、スタートアップをリアルに経験することができます。 わたしたちは、スタートアップが最初の一歩を踏み出すためのプラットフォームを目指しており、開発者やビジネスマネージャー、アントレプレナー、デザイナー、マーケター等、さまざまなスキルの人々を結びつけ、アイディアが現実になることを願ってます。

2007年アメリカ発祥のイベントで、日本では2009年の初開催以来全国様々な都市で開催されています。

Startup Weekend Ogaki 03とは

岐阜県大垣市でのStartup Weekendです。

大垣といえば古くから交通の要衝として大変栄えた街ですが、昨今ITに大変力を入れており、IT研究開発の拠点ソフトピアジャパンや、IAMASこと情報科学芸術大学院大学がとくに有名です。

今回、大垣では初の夏開催で、2日目と同日(7/30)に大垣花火大会が開催予定ということもあり、最高の夏を送ることができます。

swogaki.doorkeeper.jp

大垣とわたし

会社紹介ページにあるわたしの略歴に記載している通り生まれも育ち関西なのですが、実はこの大垣はわたしのエンジニア人生の中で大変恩義と思い出の深い街です。

2003年に大学を卒業し大阪のSIerに入社し、一番最初にアサインされたプロジェクトの場が大垣でした。初の実務、初の出張、初のホテル暮らし*2、初のカットオーバー立会い、お客様との共同作業、打ち上げなどなど、様々なことを体験しました。 当時はメインフレームを使った基幹業務システム開発に従事していたので、今のアニメイトラボとでは技術としては全く使っているものは違いますが、そのプロジェクトで叩き込まれたエンジニアとしての振る舞いや基礎の考え方は完全に今の自分のベースになっています。

そんな思い出深い大垣の地で、コーチの一員としてStartup Weekend Ogaki 03のサポートを担えるのは、大変感慨深いです。よろしくお願いします!

お申込み方法

下記サイトからお申込みください!

swogaki.doorkeeper.jp

大垣での初プロジェクトから今までのわたしのキャリアについて興味を持った方は....

8/3(水)の晩、東京・渋谷でのこのイベントでじっくりお話します。ぜひご参加ください。参加費無料です。

careerlog.doorkeeper.jp

予め、先日掲載いただいた下記のインタビューをご覧いただくと、当日120%楽しめるかと思います。

careerlog.jp

careerlog.jp

*1:https://www.facebook.com/swogaki/posts/505697909630208 より

*2:大垣駅前のビジネスホテルが定宿でした

『Pokémon GO』 を快適に遊ぶために必要な位置情報取得の実地レポ【新宿編】

 最近はiOSのアプリ開発を担当し始めた、同人誌入稿GOへ至れないエンジニアのかんがーです。
 
 2016年7月22日、本日とうとう『Pokémon GO』がリリースされましたね。海外で、色々と話題になっていただけに、僕自身強い興味がありました。

 午前10時過ぎにAndroid版がリリースされたとのことで早速自分のNexus4にダウンロードしプレイを始めたのですが、不思議な事に一向にゲームを始めることが出来ません。どうやら初期ポケモンを選ぶ際、GPSが取得できていないと先に進めないということでした。社内では『Pokémon GO』を始められ!ということで、とても悲しい気持ちになっていたんです。

f:id:animatelab:20160722182044p:plain
 
 よく考えてみたら、GPS自体はアプリエンジニアであってもそのアプリの種類によってはあまり触れない場所ではないでしょうか。今日はそんなGPSを解説しつつ、では実際の街中では実際どれくらい取得できるものなのか?実際に『Pokémon GO』を遊んでみました調べてみました。

続きを読む