Blog Infos
Author
Published
Topics
Author
Published
Topics
Posted By: Inuwa Ibrahim
👻 Hello! Lets Build a Clone of SnapChat

In this tutorial, I will walk you through on how to build an app similar in design like SnapChat on Android.

STEPS
  • Set Up Android Studio
  • Setup Dependencies and Assets
  • Set up Splash Screen
  • Setup Bottom Navigation
  • Build Camera Screen
  • Build Chat Screen
  • Build Discover Screen
  • Build Maps Screen
  • Build Stories Screen
  • Build View Stories Screen
SETUP ANDROID STUDIO
SETUP DEPENDENCIES AND ASSETS
Dependencies
  • Open build.gradle(app)
  • Inside the plugins block, add these:

 

plugins {
    id 'com.android.application'
    id 'kotlin-android'
    //add this line
    id 'kotlin-android-extensions'
    //and this line (safe args)
    id 'androidx.navigation.safeargs.kotlin'
}

 

 

buildFeatures {
    viewBinding true
}

 

//viewBinding - for view binding
implementation 'com.android.databinding:viewbinding:7.0.0'
//navigation component - for navigating between fragments
def nav_version = "2.3.5"
//Kotlin
implementation "androidx.navigation:navigation-fragment-ktx:$nav_version"
implementation "androidx.navigation:navigation-ui-ktx:$nav_version"
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
//Feature module Support
implementation "androidx.navigation:navigation-dynamic-features-fragment:$nav_version"
//Testing Navigation
androidTestImplementation "androidx.navigation:navigation-testing:$nav_version"
//lifeCycle
def lifecycle_version = "2.4.0-alpha02"
implementation "androidx.lifecycle:lifecycle-runtime-ktx:$lifecycle_version"
implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycle_version"
//google maps
implementation 'com.google.android.gms:play-services-maps:17.0.0'
//camera X
def camerax_version = "1.0.0"
// CameraX core library using camera2 implementation
implementation "androidx.camera:camera-camera2:$camerax_version"
// CameraX Lifecycle Library
implementation "androidx.camera:camera-lifecycle:$camerax_version"
// CameraX View class
implementation "androidx.camera:camera-view:1.0.0-alpha24"
//stories view
implementation 'com.github.shts:StoriesProgressView:3.0.0'
view raw build.gradle hosted with ❤ by GitHub
//Safe args navigation component
def nav_version = "2.3.4"
classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$nav_version"

 

 

maven { url “https://jitpack.io" }

 

SETUP SPLASH SCREEN
  • Add these to themes.xml (under res/values directory)

 

<!-- Splash screen theme -->
<style name="splashScreenTheme" parent="Theme.AppCompat.Light.NoActionBar">
    <item name="android:windowBackground">@drawable/splash_screen</item>
</style>

 

  • Open AndroidManifest.xml add this in mainActivity block:
    android:theme=”@style/splashScreenTheme”

SET UP BOTTOM NAVIGATION
  • We will be making use of navigation component to set up our bottom navigation, so make sure the dependencies are added.
  • First create six new fragments — Name them:
    CameraFragmentChatFragmentDiscoverFragmentMapFragment StoriesFragment and ViewStoriesFragment (The ViewStoriesFragment isn’t part of the bottom nav)
  • Right click res — Select NewNew Android Resource Directory
    Directory name — menu
    Resource type — menu
  • In the newly created menu directory, right click and select NewNew Menu resource file, name the file bottom_navigation.xml
  • In bottom_navigation.xml, add these:
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/mapFragment"
android:icon="@drawable/map_bottom_nav_selector"
android:title="@string/map"
android:menuCategory="secondary"
/>
<item
android:id="@+id/chatFragment"
android:icon="@drawable/chat_bottom_nav_selector"
android:title="@string/chat"
android:menuCategory="secondary"
/>
<item
android:id="@+id/cameraFragment"
android:icon="@drawable/camera_bottom_nav_selector"
android:title="@string/camera"
android:menuCategory="secondary"
/>
<item
android:id="@+id/storiesFragment"
android:icon="@drawable/stories_bottom_nav_selector"
android:title="@string/stories"
android:menuCategory="secondary"
/>
<item
android:id="@+id/discoverFragment"
android:icon="@drawable/discover_bottom_nav_selector"
android:title="@string/discover"
android:menuCategory="secondary" />
</menu>
  • In the newly created navigation directory, right click and select NewNew Navigation resource file, name the file nav_graph.xml
  • Now open bottom_navigation.xml file you just created and add these:
<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=".ui.activities.MainActivity">
<androidx.fragment.app.FragmentContainerView
android:id="@+id/nav_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toTopOf="@+id/bottom_navigation"
app:defaultNavHost="true"
app:navGraph="@navigation/nav_graph" />
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="@+id/bottom_navigation"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
app:itemIconSize="24dp"
app:itemTextColor="@color/white"
android:background="@color/black"
app:labelVisibilityMode="unlabeled"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:menu="@menu/bottom_navigation" />
</androidx.constraintlayout.widget.ConstraintLayout>

Now Open MainActivity.kt file, add a method for initialising and setting bottom navigation, your whole MainActivity.kt file will look like this:

class MainActivity : AppCompatActivity() {
private val binding by viewBinding(ActivityMainBinding::inflate)
lateinit var navController: NavController
override fun onCreate(savedInstanceState: Bundle?) {
setTheme(R.style.Theme_SnapchatClone)
super.onCreate(savedInstanceState)
transparentStatusBar()
setContentView(binding.root)
initViews()
}
private fun initViews(){
setUpBottomNavigation()
}
private fun setUpBottomNavigation(){
val navHost = supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment
navController = navHost.navController
binding.bottomNavigation.setupWithNavController(navController)
binding.bottomNavigation.setOnItemReselectedListener {
//do something when selected twice
}
binding.bottomNavigation.setOnItemSelectedListener { item ->
NavigationUI.onNavDestinationSelected(
item,
Navigation.findNavController(this, R.id.nav_host_fragment)
)
}
binding.bottomNavigation.itemIconTintList = null
//if we are viewing stories, hide the bottom navigation
navController.addOnDestinationChangedListener { _, destination, _ ->
if(destination.id == R.id.viewStoriesFragment) {
binding.bottomNavigation.visibility = View.GONE
} else {
binding.bottomNavigation.visibility = View.VISIBLE
}
}
}
}
view raw MainActivity.kt hosted with ❤ by GitHub

BUILD CAMERA SCREEN
  • For the main camera screen, we will be using Google’s android Camera X, so make sure the dependencies have been added
  • Open fragment_camera.xml add the following code:
<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=".ui.fragments.CameraFragment">
<ImageView
android:id="@+id/imageView29"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="10dp"
android:layout_marginTop="10dp"
android:elevation="2dp"
android:src="@drawable/ic_profile_pic_round_transparent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ImageView
android:id="@+id/imageView30"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="10dp"
android:elevation="2dp"
android:src="@drawable/ic_search_round_transparent"
app:layout_constraintBottom_toBottomOf="@+id/imageView29"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toEndOf="@+id/imageView29"
app:layout_constraintTop_toTopOf="@+id/imageView29"
app:layout_constraintVertical_bias="0.0" />
<ImageView
android:id="@+id/imageView31"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:elevation="2dp"
android:layout_marginEnd="10dp"
android:src="@drawable/ic_add_friend_round_transparent"
app:layout_constraintEnd_toStartOf="@+id/img_switch_camera"
app:layout_constraintTop_toTopOf="@+id/imageView30" />
<ImageView
android:id="@+id/img_switch_camera"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:elevation="2dp"
android:layout_marginEnd="10dp"
android:src="@drawable/ic_camera_switch_round_transparent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@+id/imageView31" />
<Button
android:id="@+id/camera_capture_button"
android:layout_width="80dp"
android:layout_height="80dp"
android:layout_marginBottom="20dp"
android:background="@drawable/btn_round"
android:elevation="2dp"
android:scaleType="fitCenter"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent" />
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:elevation="2dp"
android:layout_marginEnd="20dp"
android:src="@drawable/ic_gallery_round_transparent"
app:layout_constraintBottom_toBottomOf="@+id/camera_capture_button"
app:layout_constraintEnd_toStartOf="@+id/camera_capture_button"
app:layout_constraintHorizontal_bias="1.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@+id/camera_capture_button" />
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:elevation="2dp"
android:layout_marginStart="20dp"
android:src="@drawable/ic_filter_smile_round_transparent"
app:layout_constraintBottom_toBottomOf="@+id/camera_capture_button"
app:layout_constraintStart_toEndOf="@+id/camera_capture_button"
app:layout_constraintTop_toTopOf="@+id/camera_capture_button" />
<androidx.camera.view.PreviewView
android:id="@+id/viewFinder"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
view raw fragment_camera hosted with ❤ by GitHub
class CameraFragment : Fragment() {
//View binding, you can access your view anyhow you want
private val binding by viewBinding(FragmentCameraBinding::bind)
private var imageCapture: ImageCapture? = null
private lateinit var outputDirectory: File
private lateinit var cameraExecutor: ExecutorService
private var lensFacing = CameraSelector.DEFAULT_FRONT_CAMERA
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_camera, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
initView()
}
private fun initView(){
// Request camera permissions
if (allPermissionsGranted()) {
startCamera()
} else {
ActivityCompat.requestPermissions(
requireActivity(), REQUIRED_PERMISSIONS, REQUEST_CODE_PERMISSIONS
)
}
// Set up the listener for take photo button
binding.cameraCaptureButton.setOnClickListener {
takePhoto()
}
outputDirectory = getOutputDirectory()
cameraExecutor = Executors.newSingleThreadExecutor()
handleClicks()
}
private fun handleClicks(){
//on click switch camera
binding.imgSwitchCamera.setOnClickListener {
if (lensFacing == CameraSelector.DEFAULT_FRONT_CAMERA) lensFacing = CameraSelector.DEFAULT_BACK_CAMERA;
else if (lensFacing == CameraSelector.DEFAULT_BACK_CAMERA) lensFacing = CameraSelector.DEFAULT_FRONT_CAMERA;
startCamera()
}
}
private fun takePhoto(){
// Get a stable reference of the modifiable image capture use case
val imageCapture = imageCapture ?: return
// Create time-stamped output file to hold the image
val photoFile = File(
outputDirectory,
SimpleDateFormat(
FILENAME_FORMAT, Locale.US
).format(System.currentTimeMillis()) + ".jpg"
)
// Create output options object which contains file + metadata
val outputOptions = ImageCapture.OutputFileOptions.Builder(photoFile).build()
// Set up image capture listener, which is triggered after photo has
// been taken
imageCapture.takePicture(
outputOptions,
ContextCompat.getMainExecutor(requireContext()),
object : ImageCapture.OnImageSavedCallback {
override fun onError(exc: ImageCaptureException) {
Log.e(TAG, "Photo capture failed: ${exc.message}", exc)
}
override fun onImageSaved(output: ImageCapture.OutputFileResults) {
//the uri of photo captured here
val savedUri = Uri.fromFile(photoFile)
val msg = "Photo capture successfully"
Toast.makeText(requireContext(), msg, Toast.LENGTH_SHORT).show()
Log.d(TAG, msg)
}
})
}
private fun startCamera() {
val cameraProviderFuture = ProcessCameraProvider.getInstance(requireContext())
cameraProviderFuture.addListener(Runnable {
// Used to bind the lifecycle of cameras to the lifecycle owner
val cameraProvider: ProcessCameraProvider = cameraProviderFuture.get()
// Preview
val preview = Preview.Builder()
.build()
.also {
it.setSurfaceProvider(viewFinder.surfaceProvider)
}
imageCapture = ImageCapture.Builder()
.build()
try {
// Unbind use cases before rebinding
cameraProvider.unbindAll()
cameraProvider.bindToLifecycle(this, lensFacing, imageCapture, preview)
} catch (exc: Exception) {
Log.e(TAG, "Use case binding failed", exc)
}
}, ContextCompat.getMainExecutor(requireContext()))
}
private fun allPermissionsGranted() = REQUIRED_PERMISSIONS.all {
ContextCompat.checkSelfPermission(
requireContext(), it
) == PackageManager.PERMISSION_GRANTED
}
private fun getOutputDirectory(): File {
val mediaDir = activity?.externalMediaDirs?.firstOrNull()?.let {
File(it, resources.getString(R.string.app_name)).apply { mkdirs() } }
return if (mediaDir != null && mediaDir.exists())
mediaDir else requireActivity().filesDir
}
override fun onDestroy() {
super.onDestroy()
cameraExecutor.shutdown()
}
companion object {
private const val TAG = "CameraXBasic"
private const val FILENAME_FORMAT = "yyyy-MM-dd-HH-mm-ss-SSS"
private const val REQUEST_CODE_PERMISSIONS = 10
private val REQUIRED_PERMISSIONS = arrayOf(Manifest.permission.CAMERA)
}
override fun onRequestPermissionsResult(
requestCode: Int, permissions: Array<String>, grantResults:
IntArray
) {
if (requestCode == REQUEST_CODE_PERMISSIONS) {
if (allPermissionsGranted()) {
startCamera()
} else {
Toast.makeText(
requireContext(),
"Permissions not granted by the user.",
Toast.LENGTH_SHORT
).show()
requireActivity().finish()
}
}
}
}

BUILD CHAT SCREEN
  • For fragment_chat.xml , I wont be pasting the code for this layout here (there are over 900 lines of codes written in that file)
  • On an ideal world, a single RecyclerView will be used or a model but I had to populate the data with custom views (Not the best practice, I know 💇)
  • Here is the link to activity_chat.xmlfile:
    Click here
  • No changes were made to ChatFragment.kt file
  • Once you’ve done that, you should end up with something like this:

BUILD DISCOVER SCREEN
  • For fragment_discover.xml Check here for full code
  • No changes were made to DiscoverFragment
  • You should have something like this:

Job Offers

Job Offers

There are currently no vacancies.

OUR VIDEO RECOMMENDATION

, ,

Migrating to Jetpack Compose – an interop love story

Most of you are familiar with Jetpack Compose and its benefits. If you’re able to start anew and create a Compose-only app, you’re on the right track. But this talk might not be for you…
Watch Video

Migrating to Jetpack Compose - an interop love story

Simona Milanovic
Android DevRel Engineer for Jetpack Compose
Google

Migrating to Jetpack Compose - an interop love story

Simona Milanovic
Android DevRel Engin ...
Google

Migrating to Jetpack Compose - an interop love story

Simona Milanovic
Android DevRel Engineer f ...
Google

Jobs

BUILD MAPS SCREEN
  • 🗺 SnapMap — Spoiler 👽 (I could not make a custom map view similar to SnapChat, I ended up using Google Maps)
  • Watch this video on how to set up Google Maps
  • Open fragment_map.xml
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@null"
xmlns:app="http://schemas.android.com/apk/res-auto"
tools:context=".ui.fragments.MapFragment">
<androidx.fragment.app.FragmentContainerView
android:id="@+id/map"
android:name="com.google.android.gms.maps.SupportMapFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scrollbars="vertical"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
</androidx.fragment.app.FragmentContainerView>
<ImageView
android:id="@+id/ic_profile_pic"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:elevation="2dp"
android:layout_marginTop="10dp"
android:layout_marginStart="10dp"
android:src="@drawable/ic_profile_pic_round_transparent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ImageView
android:id="@+id/imageView28"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity=""
android:layout_marginStart="10dp"
android:src="@drawable/ic_search_round_transparent"
app:layout_constraintStart_toEndOf="@+id/ic_profile_pic"
app:layout_constraintTop_toTopOf="@+id/ic_profile_pic" />
<TextView
android:id="@+id/textView33"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:fontFamily="@font/avenir_black"
android:gravity="center"
android:text="@string/snap_map"
android:textColor="@color/white"
android:textSize="18sp"
android:textStyle="bold"
app:layout_constraintBottom_toBottomOf="@+id/imageView28"
app:layout_constraintEnd_toStartOf="@+id/settings"
app:layout_constraintStart_toEndOf="@+id/imageView28"
app:layout_constraintTop_toTopOf="@+id/imageView28" />
<ImageView
android:id="@+id/settings"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:layout_marginEnd="10dp"
android:src="@drawable/ic_settings_round_transparent"
app:layout_constraintBottom_toBottomOf="@+id/textView33"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@+id/textView33" />
</androidx.constraintlayout.widget.ConstraintLayout>
class MapFragment : Fragment() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
var root = inflater.inflate(R.layout.fragment_map, container, false)
val mapFragment = childFragmentManager.findFragmentById(R.id.map) as SupportMapFragment
mapFragment.getMapAsync { map->
map.setOnMapClickListener { lat->
//when clicked, initialize marker options
val markerOptions = MarkerOptions()
markerOptions.position(lat)
markerOptions.title(lat.latitude.toString() + lat.longitude.toString())
//remove marker
map.clear()
//animate to zoom marker
map.animateCamera(CameraUpdateFactory.newLatLngZoom(lat, 10F))
//ad marker on map
map.addMarker(markerOptions)
}
}
return root
}
}
view raw MapFragment.kt hosted with ❤ by GitHub

BUILD STORIES SCREEN
  • Click this link for full fragment_stories.xml code
  • Open FragmentStories.kt file, we are going to be setting up click event on the stories view to ViewStoriesFragment
  • Full FragmentStories.kt code
class StoriesFragment : Fragment() {
private val binding by viewBinding(FragmentStoriesBinding::bind)
lateinit var navController: NavController
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_stories, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
navController = Navigation.findNavController(view)
initView()
}
private fun initView(){
handleClicks()
}
private fun handleClicks(){
//on click stories, go to view stories fragment
binding.storiesView.setOnClickListener {
val action = StoriesFragmentDirections.actionStoriesFragmentToViewStoriesFragment()
navController.navigate(action)
}
}
}
BUILD VIEW STORIES SCREEN
  • To implement stories kind of design, I used a library: StoriesProgressView
  • Open fragment_view_stories.xml use the following code for your layout:
<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=".ui.fragments.ViewStoriesFragment">
<ImageView
android:id="@+id/image"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:contentDescription="@null"
android:scaleType="centerCrop"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal">
<View
android:id="@+id/reverse"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1" />
<View
android:id="@+id/skip"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1" />
</LinearLayout>
<jp.shts.android.storiesprogressview.StoriesProgressView
android:id="@+id/stories"
android:layout_width="match_parent"
android:layout_height="3dp"
android:layout_gravity="top"
android:paddingLeft="8dp"
android:paddingRight="8dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ImageView
android:id="@+id/imageView19"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_marginTop="10dp"
android:src="@drawable/ic_person_3"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.04"
app:layout_constraintStart_toStartOf="@+id/stories"
app:layout_constraintTop_toBottomOf="@+id/stories" />
<TextView
android:id="@+id/textView34"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="4dp"
android:layout_marginEnd="92dp"
android:text="@string/ibrajix"
android:textColor="@color/white"
android:textSize="12sp"
android:textStyle="bold"
app:layout_constraintBottom_toBottomOf="@+id/imageView19"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toEndOf="@+id/imageView19"
app:layout_constraintTop_toTopOf="@+id/imageView19" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="6dp"
android:fontFamily="@font/avenir_book"
android:text="@string/_4h"
android:textColor="@color/light_grey"
android:textSize="10sp"
app:layout_constraintBottom_toBottomOf="@+id/textView34"
app:layout_constraintStart_toEndOf="@+id/textView34"
app:layout_constraintTop_toTopOf="@+id/textView34" />
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="0dp"
android:layout_height="50dp"
android:background="@color/black"
android:paddingStart="20dp"
android:paddingEnd="20dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent">
<ImageView
android:id="@+id/imageView32"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_camera_stories_round"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<EditText
android:layout_width="0dp"
android:layout_height="40dp"
android:background="@drawable/et_rounded"
android:layout_marginStart="4dp"
android:hint="@string/send_a_chat"
android:textColorHint="@color/white"
android:textColor="@color/white"
android:paddingStart="20dp"
android:paddingEnd="20dp"
android:textSize="12sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/imageView32"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
class ViewStoriesFragment : Fragment(), StoriesProgressView.StoriesListener {
private val binding by viewBinding(FragmentViewStoriesBinding::bind)
lateinit var navController: NavController
private var counter = 0
//populate stories with these drawables
private val resources = intArrayOf(
R.drawable.discover_2,
R.drawable.discover_1,
R.drawable.stories_2,
R.drawable.subscription_img,
R.drawable.stories_3
)
private var pressTime = 0L
private var limit = 500L
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_view_stories, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
activity?.window?.addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN)
navController = Navigation.findNavController(view)
initView()
}
private fun initView(){
showStories()
handleClicks()
}
private fun handleClicks(){
//on click previous
binding.reverse.setOnClickListener { binding.stories.reverse() }
binding.reverse.setOnTouchListener(onTouchListener)
//on click skip
binding.skip.setOnClickListener { binding.stories.skip() }
binding.skip.setOnTouchListener(onTouchListener)
}
private fun showStories(){
binding.stories.setStoriesCount(PROGRESS_COUNT)
binding.stories.setStoryDuration(3000L)
binding.stories.setStoriesListener(this)
counter = 0
binding.stories.startStories(counter)
binding.image.setImageResource(resources[counter])
}
override fun onNext() {
binding.image.setImageResource(resources[++counter])
}
override fun onPrev() {
if (counter - 1 < 0) return
binding.image.setImageResource(resources[--counter])
}
override fun onComplete() {
navController.popBackStack()
}
@SuppressLint("ClickableViewAccessibility")
private val onTouchListener = OnTouchListener { v, event ->
when (event.action) {
MotionEvent.ACTION_DOWN -> {
pressTime = System.currentTimeMillis()
binding.stories.pause()
return@OnTouchListener false
}
MotionEvent.ACTION_UP -> {
val now = System.currentTimeMillis()
binding.stories.resume()
return@OnTouchListener limit < now - pressTime
}
}
false
}
companion object {
private const val PROGRESS_COUNT = 5
}
}

CONTACT ME

Twitter:
https://twitter.com/ibrajix

YOU MAY BE INTERESTED IN

YOU MAY BE INTERESTED IN

blog
It’s one of the common UX across apps to provide swipe to dismiss so…
READ MORE
blog
In this part of our series on introducing Jetpack Compose into an existing project,…
READ MORE
blog
This is the second article in an article series that will discuss the dependency…
READ MORE
blog
Let’s suppose that for some reason we are interested in doing some tests with…
READ MORE

Leave a Reply

Your email address will not be published. Required fields are marked *

Fill out this field
Fill out this field
Please enter a valid email address.

Menu