Code Example

Kotlin Android Highlight App Parts Snippets

A step by step Android Highlight App Parts example.

1. TakuSemba/Spotlight

Android Library that lights items for tutorials or walk-throughs etc....

Step 1: Gradle

dependencies {
    implementation 'com.github.takusemba:spotlight:x.x.x'
}

Step 2: Usage

val spotlight = Spotlight.Builder(this)
    .setTargets(firstTarget, secondTarget, thirdTarget ...)
    .setBackgroundColor(R.color.spotlightBackground)
    .setDuration(1000L)
    .setAnimation(DecelerateInterpolator(2f))
    .setContainer(viewGroup)
    .setOnSpotlightListener(object : OnSpotlightListener {
      override fun onStarted() {
        Toast.makeText([email protected], "spotlight is started", Toast.LENGTH_SHORT).show()
      }
      override fun onEnded() {
        Toast.makeText([email protected], "spotlight is ended", Toast.LENGTH_SHORT).show()
      }
    })
    .build()

If you want to show Spotlight immediately, you have to wait until views are laid out.

// with core-ktx method.
view.doOnPreDraw { Spotlight.Builder(this)...start() }

Spotlight Example Tutorial

Step 3: Target

Create a Target to add Spotlight.
Target is a spot to be casted by Spotlight. You can add multiple targets to Spotlight.

val target = Target.Builder()
    .setAnchor(100f, 100f)
    .setShape(Circle(100f))
    .setEffect(RippleEffect(100f, 200f, argb(30, 124, 255, 90)))
    .setOverlay(layout)
    .setOnTargetListener(object : OnTargetListener {
      override fun onStarted() {
        makeText([email protected], "first target is started", LENGTH_SHORT).show()
      }
      override fun onEnded() {
        makeText([email protected], "first target is ended", LENGTH_SHORT).show()
      }
    })
    .build()

Step 4: Start/Finish Spotlight

val spotlight = Spotlight.Builder(this)...start()

spotlight.finish()

Step 5: Next/Previous/Show Target

val spotlight = Spotlight.Builder(this)...start()

spotlight.next()

spotlight.previous()

spotlight.show(2)

Step 6: Custom Shape

class CustomShape(
    override val duration: Long,
    override val interpolator: TimeInterpolator
) : Shape {

  override fun draw(canvas: Canvas, point: PointF, value: Float, paint: Paint) {
    // draw your shape here.
  }
}

Step 7: Custom Effect

class CustomEffect(
    override val duration: Long,
    override val interpolator: TimeInterpolator,
    override val repeatMode: Int
) : Effect {

  override fun draw(canvas: Canvas, point: PointF, value: Float, paint: Paint) {
    // draw your effect here.
  }
}

Full Example

Here is a full example project:

(b). ActivitySampleActivity.kt

Here is the full code for our ActivitySampleActivity.kt file:

package com.takusemba.spotlightsample

import android.os.Bundle
import android.view.View
import android.view.animation.DecelerateInterpolator
import android.widget.FrameLayout
import android.widget.Toast
import android.widget.Toast.LENGTH_SHORT
import android.widget.Toast.makeText
import androidx.appcompat.app.AppCompatActivity
import com.takusemba.spotlight.OnSpotlightListener
import com.takusemba.spotlight.OnTargetListener
import com.takusemba.spotlight.Spotlight
import com.takusemba.spotlight.Target
import com.takusemba.spotlight.shape.Circle

class ActivitySampleActivity : AppCompatActivity(R.layout.activity_activity_sample) {

  private var currentToast: Toast? = null

  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    findViewById<View>(R.id.start).setOnClickListener { startButton ->
      val targets = ArrayList<Target>()

      // first target
      val firstRoot = FrameLayout(this)
      val first = layoutInflater.inflate(R.layout.layout_target, firstRoot)
      val firstTarget = Target.Builder()
          .setAnchor(findViewById<View>(R.id.one))
          .setShape(Circle(100f))
          .setOverlay(first)
          .setOnTargetListener(object : OnTargetListener {
            override fun onStarted() {
              currentToast?.cancel()
              currentToast = makeText(
                  [email protected],
                  "first target is started",
                  LENGTH_SHORT
              )
              currentToast?.show()
            }

            override fun onEnded() {
              currentToast?.cancel()
              currentToast = makeText(
                  [email protected],
                  "first target is ended",
                  LENGTH_SHORT
              )
              currentToast?.show()
            }
          })
          .build()

      targets.add(firstTarget)

      // second target
      val secondRoot = FrameLayout(this)
      val second = layoutInflater.inflate(R.layout.layout_target, secondRoot)
      val secondTarget = Target.Builder()
          .setAnchor(findViewById<View>(R.id.two))
          .setShape(Circle(150f))
          .setOverlay(second)
          .setOnTargetListener(object : OnTargetListener {
            override fun onStarted() {
              currentToast?.cancel()
              currentToast = makeText(
                  [email protected],
                  "second target is started",
                  LENGTH_SHORT
              )
              currentToast?.show()
            }

            override fun onEnded() {
              currentToast?.cancel()
              currentToast = makeText(
                  [email protected],
                  "second target is ended",
                  LENGTH_SHORT
              )
              currentToast?.show()
            }
          })
          .build()

      targets.add(secondTarget)

      // third target
      val thirdRoot = FrameLayout(this)
      val third = layoutInflater.inflate(R.layout.layout_target, thirdRoot)
      val thirdTarget = Target.Builder()
          .setAnchor(findViewById<View>(R.id.three))
          .setShape(Circle(200f))
          .setOverlay(third)
          .setOnTargetListener(object : OnTargetListener {
            override fun onStarted() {
              currentToast?.cancel()
              currentToast = makeText(
                  [email protected],
                  "third target is started",
                  LENGTH_SHORT
              )
              currentToast?.show()
            }

            override fun onEnded() {
              currentToast?.cancel()
              currentToast = makeText(
                  [email protected],
                  "third target is ended",
                  LENGTH_SHORT
              )
              currentToast?.show()
            }
          })
          .build()

      targets.add(thirdTarget)

      // create spotlight
      val spotlight = Spotlight.Builder([email protected])
          .setTargets(targets)
          .setBackgroundColorRes(R.color.spotlightBackground)
          .setDuration(1000L)
          .setAnimation(DecelerateInterpolator(2f))
          .setOnSpotlightListener(object : OnSpotlightListener {
            override fun onStarted() {
              currentToast?.cancel()
              currentToast = makeText(
                  [email protected],
                  "spotlight is started",
                  LENGTH_SHORT
              )
              currentToast?.show()
              startButton.isEnabled = false
            }

            override fun onEnded() {
              currentToast?.cancel()
              currentToast = makeText(
                  [email protected],
                  "spotlight is ended",
                  LENGTH_SHORT
              )
              currentToast?.show()
              startButton.isEnabled = true
            }
          })
          .build()

      spotlight.start()

      val nextTarget = View.OnClickListener { spotlight.next() }

      val closeSpotlight = View.OnClickListener { spotlight.finish() }

      first.findViewById<View>(R.id.close_target).setOnClickListener(nextTarget)
      second.findViewById<View>(R.id.close_target).setOnClickListener(nextTarget)
      third.findViewById<View>(R.id.close_target).setOnClickListener(nextTarget)

      first.findViewById<View>(R.id.close_spotlight).setOnClickListener(closeSpotlight)
      second.findViewById<View>(R.id.close_spotlight).setOnClickListener(closeSpotlight)
      third.findViewById<View>(R.id.close_spotlight).setOnClickListener(closeSpotlight)
    }
  }
}

(c). ChooserActivity.kt

Here is the full code for our ChooserActivity.kt file:

package com.takusemba.spotlightsample

import android.content.Intent
import android.os.Bundle
import android.widget.ArrayAdapter
import android.widget.ListView
import androidx.appcompat.app.AppCompatActivity

class ChooserActivity : AppCompatActivity(R.layout.activity_chooser) {

  private val samples: Array<String> = arrayOf(
      SAMPLE_SPOTLIGHT_ON_ACTIVITY,
      SAMPLE_SPOTLIGHT_ON_FRAGMENT
  )

  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    val listView = findViewById<ListView>(R.id.sample_list)
    val adapter = ArrayAdapter(this, android.R.layout.simple_list_item_1, samples)
    listView.adapter = adapter
    listView.setOnItemClickListener { _, _, position, _ ->
      when (samples[position]) {
        SAMPLE_SPOTLIGHT_ON_ACTIVITY -> {
          val intent = Intent(this, ActivitySampleActivity::class.java)
          startActivity(intent)
        }
        SAMPLE_SPOTLIGHT_ON_FRAGMENT -> {
          val intent = Intent(this, FragmentSampleActivity::class.java)
          startActivity(intent)
        }
      }
    }
  }

  companion object {
    private const val SAMPLE_SPOTLIGHT_ON_ACTIVITY = "Spotlight on Activity"
    private const val SAMPLE_SPOTLIGHT_ON_FRAGMENT = "Spotlight on Fragment"
  }
}

(d). FragmentSampleActivity.kt

Here is the full code for our FragmentSampleActivity.kt file:

package com.takusemba.spotlightsample

import androidx.appcompat.app.AppCompatActivity

class FragmentSampleActivity : AppCompatActivity(R.layout.activity_fragment_sample)

(e). FragmentSampleFragment.kt

Here is the full code for our FragmentSampleFragment.kt file:

package com.takusemba.spotlightsample

import android.os.Bundle
import android.view.View
import android.view.animation.DecelerateInterpolator
import android.widget.FrameLayout
import android.widget.Toast
import androidx.fragment.app.Fragment
import com.takusemba.spotlight.OnSpotlightListener
import com.takusemba.spotlight.OnTargetListener
import com.takusemba.spotlight.Spotlight
import com.takusemba.spotlight.Target
import com.takusemba.spotlight.shape.Circle

class FragmentSampleFragment : Fragment(R.layout.fragment_fragment_sample) {

  private var currentToast: Toast? = null

  override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)
    view.findViewById<View>(R.id.start).setOnClickListener { startButton ->
      val targets = ArrayList<Target>()

      // first target
      val firstRoot = FrameLayout(requireContext())
      val first = layoutInflater.inflate(R.layout.layout_target, firstRoot)
      val firstTarget = Target.Builder()
          .setAnchor(view.findViewById<View>(R.id.one))
          .setShape(Circle(100f))
          .setOverlay(first)
          .setOnTargetListener(object : OnTargetListener {
            override fun onStarted() {
              currentToast?.cancel()
              currentToast = Toast.makeText(
                  requireContext(),
                  "first target is started",
                  Toast.LENGTH_SHORT
              )
              currentToast?.show()
            }

            override fun onEnded() {
              currentToast?.cancel()
              currentToast = Toast.makeText(
                  requireContext(),
                  "first target is ended",
                  Toast.LENGTH_SHORT
              )
              currentToast?.show()
            }
          })
          .build()

      targets.add(firstTarget)

      // second target
      val secondRoot = FrameLayout(requireActivity())
      val second = layoutInflater.inflate(R.layout.layout_target, secondRoot)
      val secondTarget = Target.Builder()
          .setAnchor(view.findViewById<View>(R.id.two))
          .setShape(Circle(150f))
          .setOverlay(second)
          .setOnTargetListener(object : OnTargetListener {
            override fun onStarted() {
              currentToast?.cancel()
              currentToast = Toast.makeText(
                  requireContext(),
                  "second target is started",
                  Toast.LENGTH_SHORT
              )
              currentToast?.show()
            }

            override fun onEnded() {
              currentToast?.cancel()
              currentToast = Toast.makeText(
                  requireContext(),
                  "second target is ended",
                  Toast.LENGTH_SHORT
              )
              currentToast?.show()
            }
          })
          .build()

      targets.add(secondTarget)

      // third target
      val thirdRoot = FrameLayout(requireContext())
      val third = layoutInflater.inflate(R.layout.layout_target, thirdRoot)
      val thirdTarget = Target.Builder()
          .setAnchor(view.findViewById<View>(R.id.three))
          .setShape(Circle(200f))
          .setOverlay(third)
          .setOnTargetListener(object : OnTargetListener {
            override fun onStarted() {
              currentToast?.cancel()
              currentToast = Toast.makeText(
                  requireContext(),
                  "third target is started",
                  Toast.LENGTH_SHORT
              )
              currentToast?.show()
            }

            override fun onEnded() {
              currentToast?.cancel()
              currentToast = Toast.makeText(
                  requireContext(),
                  "third target is ended",
                  Toast.LENGTH_SHORT
              )
              currentToast?.show()
            }
          })
          .build()

      targets.add(thirdTarget)

      // create spotlight
      val spotlight = Spotlight.Builder(requireActivity())
          .setTargets(targets)
          .setBackgroundColorRes(R.color.spotlightBackground)
          .setDuration(1000L)
          .setAnimation(DecelerateInterpolator(2f))
          .setOnSpotlightListener(object : OnSpotlightListener {
            override fun onStarted() {
              currentToast?.cancel()
              currentToast = Toast.makeText(
                  requireContext(),
                  "spotlight is started",
                  Toast.LENGTH_SHORT
              )
              currentToast?.show()
              startButton.isEnabled = false
            }

            override fun onEnded() {
              currentToast?.cancel()
              currentToast = Toast.makeText(
                  requireContext(),
                  "spotlight is ended",
                  Toast.LENGTH_SHORT
              )
              currentToast?.show()
              startButton.isEnabled = true
            }
          })
          .build()

      spotlight.start()

      val nextTarget = View.OnClickListener { spotlight.next() }

      val closeSpotlight = View.OnClickListener { spotlight.finish() }

      first.findViewById<View>(R.id.close_target).setOnClickListener(nextTarget)
      second.findViewById<View>(R.id.close_target).setOnClickListener(nextTarget)
      third.findViewById<View>(R.id.close_target).setOnClickListener(nextTarget)

      first.findViewById<View>(R.id.close_spotlight).setOnClickListener(closeSpotlight)
      second.findViewById<View>(R.id.close_spotlight).setOnClickListener(closeSpotlight)
      third.findViewById<View>(R.id.close_spotlight).setOnClickListener(closeSpotlight)
    }
  }
}

(f). layout_target.xml

Here is the full code for our layout_target.xml file:

<?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"
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  >

  <TextView
    android:id="@+id/custom_text"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_marginBottom="8dp"
    android:gravity="center"
    android:text="this is a custom text"
    android:textAlignment="center"
    android:textColor="@android:color/white"
    android:textSize="24dp"
    android:textStyle="bold"
    app:layout_constraintBottom_toTopOf="@+id/close_target"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintStart_toStartOf="parent"
    />

  <Button
    android:id="@+id/close_target"
    android:layout_width="0dp"
    android:layout_height="wrap_content"
    android:layout_marginBottom="8dp"
    android:layout_marginEnd="16dp"
    android:layout_marginStart="16dp"
    android:text="@string/next_target"
    app:layout_constraintBottom_toBottomOf="parent"
    app:layout_constraintBottom_toTopOf="@+id/close_spotlight"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintStart_toStartOf="parent"
    />

  <Button
    android:id="@+id/close_spotlight"
    android:layout_width="0dp"
    android:layout_height="wrap_content"
    android:layout_marginBottom="60dp"
    android:layout_marginEnd="16dp"
    android:layout_marginStart="16dp"
    android:text="@string/close_spotlight"
    app:layout_constraintBottom_toBottomOf="parent"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintStart_toStartOf="parent"
    />

</androidx.constraintlayout.widget.ConstraintLayout>

(g). fragment_fragment_sample.xml

Here is the full code for our fragment_fragment_sample.xml file:

<?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"
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  >

  <TextView
    android:id="@+id/one"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="@string/one"
    android:textColor="@android:color/white"
    android:textSize="36sp"
    android:textStyle="bold"
    app:layout_constraintBottom_toTopOf="@+id/three"
    app:layout_constraintEnd_toStartOf="@+id/three"
    app:layout_constraintHorizontal_bias="0.5"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toBottomOf="@+id/two"
    />

  <TextView
    android:id="@+id/two"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="@string/two"
    android:textColor="@android:color/white"
    android:textSize="24sp"
    android:textStyle="bold"
    app:layout_constraintBottom_toTopOf="@+id/one"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintHorizontal_bias="0.5"
    app:layout_constraintStart_toEndOf="@+id/three"
    app:layout_constraintTop_toTopOf="parent"
    app:layout_constraintVertical_chainStyle="spread"
    />

  <TextView
    android:id="@+id/three"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="@string/three"
    android:textColor="@android:color/white"
    android:textSize="58sp"
    android:textStyle="bold"
    app:layout_constraintBottom_toTopOf="@+id/start"
    app:layout_constraintEnd_toStartOf="@+id/two"
    app:layout_constraintHorizontal_bias="0.5"
    app:layout_constraintStart_toEndOf="@+id/one"
    app:layout_constraintTop_toBottomOf="@+id/one"
    />

  <Button
    android:id="@+id/start"
    android:layout_width="0dp"
    android:layout_height="wrap_content"
    android:layout_alignParentBottom="true"
    android:layout_centerHorizontal="true"
    android:layout_marginBottom="20dp"
    android:layout_marginEnd="16dp"
    android:layout_marginStart="16dp"
    android:text="@string/start_spotlight"
    app:layout_constraintBottom_toBottomOf="parent"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintStart_toStartOf="parent"
    />

  <!--
   You can use this view to try a custom container.
   Spotlight.Builder(this).setContainer(findViewById(R.id.container))
  -->
  <FrameLayout
    android:id="@+id/container"
    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"
    />

</androidx.constraintlayout.widget.ConstraintLayout>

(h). activity_fragment_sample.xml

Here is the full code for our activity_fragment_sample.xml file:

<?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"
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  >

  <fragment
    android:id="@+id/fragment"
    android:name="com.takusemba.spotlightsample.FragmentSampleFragment"
    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"
    />

</androidx.constraintlayout.widget.ConstraintLayout>

(i). activity_chooser.xml

Here is the full code for our activity_chooser.xml file:

<?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"
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  >

  <ListView
    android:id="@+id/sample_list"
    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"
    />

</androidx.constraintlayout.widget.ConstraintLayout>

(j). activity_activity_sample.xml

Here is the full code for our activity_activity_sample.xml file:

<?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"
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  >

  <TextView
    android:id="@+id/one"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="@string/one"
    android:textColor="@android:color/white"
    android:textSize="36sp"
    android:textStyle="bold"
    app:layout_constraintBottom_toTopOf="@+id/three"
    app:layout_constraintEnd_toStartOf="@+id/three"
    app:layout_constraintHorizontal_bias="0.5"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toBottomOf="@+id/two"
    />

  <TextView
    android:id="@+id/two"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="@string/two"
    android:textColor="@android:color/white"
    android:textSize="24sp"
    android:textStyle="bold"
    app:layout_constraintBottom_toTopOf="@+id/one"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintHorizontal_bias="0.5"
    app:layout_constraintStart_toEndOf="@+id/three"
    app:layout_constraintTop_toTopOf="parent"
    app:layout_constraintVertical_chainStyle="spread"
    />

  <TextView
    android:id="@+id/three"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="@string/three"
    android:textColor="@android:color/white"
    android:textSize="58sp"
    android:textStyle="bold"
    app:layout_constraintBottom_toTopOf="@+id/start"
    app:layout_constraintEnd_toStartOf="@+id/two"
    app:layout_constraintHorizontal_bias="0.5"
    app:layout_constraintStart_toEndOf="@+id/one"
    app:layout_constraintTop_toBottomOf="@+id/one"
    />

  <Button
    android:id="@+id/start"
    android:layout_width="0dp"
    android:layout_height="wrap_content"
    android:layout_alignParentBottom="true"
    android:layout_centerHorizontal="true"
    android:layout_marginBottom="20dp"
    android:layout_marginEnd="16dp"
    android:layout_marginStart="16dp"
    android:text="@string/start_spotlight"
    app:layout_constraintBottom_toBottomOf="parent"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintStart_toStartOf="parent"
    />

  <!--
   You can use this view to try a custom container.
   Spotlight.Builder(this).setContainer(findViewById(R.id.container))
  -->
  <FrameLayout
    android:id="@+id/container"
    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"
    />

</androidx.constraintlayout.widget.ConstraintLayout>

Reference

You can DOWNLOAD FULL CODE.
You can also browse code or read more here.
Follow code author here.


Read More.

Related Posts