add gallery fullscreen view

This commit is contained in:
Johan von Forstner
2020-03-29 22:07:29 +02:00
parent 8c76393c53
commit de59dda16e
10 changed files with 329 additions and 16 deletions

View File

@@ -0,0 +1,106 @@
package com.johan.evmap
import android.app.Activity
import android.content.Intent
import android.os.Bundle
import android.view.View
import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.ActivityCompat
import androidx.core.app.SharedElementCallback
import androidx.databinding.DataBindingUtil
import androidx.viewpager2.widget.ViewPager2
import com.johan.evmap.adapter.GalleryAdapter
import com.johan.evmap.adapter.galleryTransitionName
import com.johan.evmap.databinding.ActivityGalleryBinding
import com.ortiz.touchview.TouchImageView
class GalleryActivity : AppCompatActivity() {
companion object {
const val EXTRA_POSITION = "position"
const val EXTRA_PHOTOS = "photos"
private const val SAVED_CURRENT_PAGE_POSITION = "current_page_position"
}
private lateinit var binding: ActivityGalleryBinding
private var isReturning: Boolean = false
private var startingPosition: Int = 0
private var currentPosition: Int = 0
private lateinit var galleryAdapter: GalleryAdapter
private var currentPage: TouchImageView? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(this, R.layout.activity_gallery)
ActivityCompat.postponeEnterTransition(this)
ActivityCompat.setEnterSharedElementCallback(this, enterElementCallback)
startingPosition = intent.getIntExtra(EXTRA_POSITION, 0)
currentPosition =
savedInstanceState?.getInt(SAVED_CURRENT_PAGE_POSITION) ?: startingPosition
galleryAdapter = GalleryAdapter(this, detailView = true, pageToLoad = currentPosition) {
ActivityCompat.startPostponedEnterTransition(this)
}
binding.gallery.setPageTransformer { page, position ->
val v = page as TouchImageView
currentPage = v
}
binding.gallery.adapter = galleryAdapter
binding.photos = intent.getParcelableArrayListExtra(EXTRA_PHOTOS)
binding.gallery.post {
binding.gallery.setCurrentItem(currentPosition, false)
binding.gallery.registerOnPageChangeCallback(object :
ViewPager2.OnPageChangeCallback() {
override fun onPageSelected(position: Int) {
currentPosition = position
}
})
}
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
outState.putInt(SAVED_CURRENT_PAGE_POSITION, currentPosition)
}
override fun finishAfterTransition() {
isReturning = true
val data = Intent()
data.putExtra(MapsActivity.EXTRA_STARTING_GALLERY_POSITION, startingPosition)
data.putExtra(MapsActivity.EXTRA_CURRENT_GALLERY_POSITION, currentPosition)
setResult(Activity.RESULT_OK, data)
super.finishAfterTransition()
}
private val enterElementCallback: SharedElementCallback = object : SharedElementCallback() {
override fun onMapSharedElements(
names: MutableList<String>,
sharedElements: MutableMap<String, View>
) {
if (isReturning) {
val index = binding.gallery.currentItem
val currentPage = currentPage ?: return
if (startingPosition != currentPosition) {
names.clear()
names.add(galleryTransitionName(index))
sharedElements.clear()
sharedElements[galleryTransitionName(index)] = currentPage
}
}
}
}
override fun onBackPressed() {
val image = currentPage
if (image == null || image.currentZoom in 0.95f..1.05f) {
super.onBackPressed()
} else {
image.setZoomAnimated(1f, 0.5f, 0.5f)
}
}
}

View File

@@ -1,14 +1,19 @@
package com.johan.evmap
import android.Manifest
import android.annotation.SuppressLint
import android.content.Intent
import android.content.pm.PackageManager
import android.content.res.Configuration
import android.net.Uri
import android.os.Bundle
import android.view.View
import android.view.ViewTreeObserver
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.ActivityCompat
import androidx.core.app.ActivityOptionsCompat
import androidx.core.app.SharedElementCallback
import androidx.core.content.ContextCompat
import androidx.databinding.DataBindingUtil
import androidx.lifecycle.MutableLiveData
@@ -27,6 +32,7 @@ import com.google.android.material.snackbar.Snackbar
import com.johan.evmap.adapter.ConnectorAdapter
import com.johan.evmap.adapter.DetailAdapter
import com.johan.evmap.adapter.GalleryAdapter
import com.johan.evmap.adapter.galleryTransitionName
import com.johan.evmap.api.*
import com.johan.evmap.databinding.ActivityMapsBinding
import com.johan.evmap.ui.ClusterIconGenerator
@@ -61,17 +67,21 @@ class MapsActivity : AppCompatActivity(), OnMapReadyCallback {
private var markers: Map<Marker, ChargeLocation> = emptyMap()
private var clusterMarkers: List<Marker> = emptyList()
private var reenterState: Bundle? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
fusedLocationClient = LocationServices.getFusedLocationProviderClient(this)
api = GoingElectricApi.create(getString(R.string.goingelectric_key))
binding = DataBindingUtil.setContentView(this, R.layout.activity_maps)
binding.lifecycleOwner = this
binding.vm = vm
fusedLocationClient = LocationServices.getFusedLocationProviderClient(this)
ActivityCompat.setExitSharedElementCallback(this, exitElementCallback)
val mapFragment = supportFragmentManager.findFragmentById(R.id.map) as SupportMapFragment
mapFragment.getMapAsync(this)
api = GoingElectricApi.create(getString(R.string.goingelectric_key))
setupAdapters()
@@ -144,8 +154,22 @@ class MapsActivity : AppCompatActivity(), OnMapReadyCallback {
}
private fun setupAdapters() {
val galleryClickListener = object : GalleryAdapter.ItemClickListener {
override fun onItemClick(view: View, position: Int) {
val photos = vm.charger.value?.photos ?: return
val intent = Intent(this@MapsActivity, GalleryActivity::class.java).apply {
putExtra(GalleryActivity.EXTRA_PHOTOS, ArrayList<ChargerPhoto>(photos))
putExtra(GalleryActivity.EXTRA_POSITION, position)
}
val options = ActivityOptionsCompat.makeSceneTransitionAnimation(
this@MapsActivity, view, view.transitionName
)
startActivity(intent, options.toBundle())
}
}
binding.gallery.apply {
adapter = GalleryAdapter(this@MapsActivity)
adapter = GalleryAdapter(this@MapsActivity, galleryClickListener)
layoutManager =
LinearLayoutManager(this@MapsActivity, LinearLayoutManager.HORIZONTAL, false)
addItemDecoration(DividerItemDecoration(
@@ -211,6 +235,7 @@ class MapsActivity : AppCompatActivity(), OnMapReadyCallback {
}
}
@SuppressLint("MissingPermission")
private fun enableLocation(animate: Boolean) {
val map = this.map ?: return
map.isMyLocationEnabled = true
@@ -333,4 +358,58 @@ class MapsActivity : AppCompatActivity(), OnMapReadyCallback {
else -> super.onRequestPermissionsResult(requestCode, permissions, grantResults)
}
}
private val exitElementCallback = object : SharedElementCallback() {
override fun onMapSharedElements(
names: MutableList<String>,
sharedElements: MutableMap<String, View>
) {
if (reenterState != null) {
val startingPosition = reenterState!!.getInt(EXTRA_STARTING_GALLERY_POSITION)
val currentPosition = reenterState!!.getInt(EXTRA_CURRENT_GALLERY_POSITION)
if (startingPosition != currentPosition) {
// Current element has changed, need to override previous exit transitions
val newTransitionName = galleryTransitionName(currentPosition)
val newSharedElement =
binding.gallery.findViewHolderForAdapterPosition(currentPosition)?.itemView
if (newSharedElement != null) {
names.clear()
names.add(newTransitionName)
sharedElements.clear()
sharedElements[newTransitionName] = newSharedElement
}
}
reenterState = null
}
}
}
override fun onActivityReenter(resultCode: Int, data: Intent) {
// returning to gallery
super.onActivityReenter(resultCode, data)
reenterState = Bundle(data.extras)
reenterState?.let {
val startingPosition = it.getInt(EXTRA_STARTING_GALLERY_POSITION)
val currentPosition = it.getInt(EXTRA_CURRENT_GALLERY_POSITION)
if (startingPosition != currentPosition) binding.gallery.scrollToPosition(
currentPosition
)
ActivityCompat.postponeEnterTransition(this)
binding.gallery.viewTreeObserver.addOnPreDrawListener(object :
ViewTreeObserver.OnPreDrawListener {
override fun onPreDraw(): Boolean {
binding.gallery.viewTreeObserver.removeOnPreDrawListener(this)
ActivityCompat.startPostponedEnterTransition(this@MapsActivity)
return true
}
})
}
}
companion object {
const val EXTRA_STARTING_GALLERY_POSITION = "extra_starting_item_position"
const val EXTRA_CURRENT_GALLERY_POSITION = "extra_current_item_position"
}
}

View File

@@ -1,41 +1,122 @@
package com.johan.evmap.adapter
import android.annotation.SuppressLint
import android.content.Context
import android.view.LayoutInflater
import android.view.ViewGroup
import android.view.*
import android.widget.ImageView
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import com.johan.evmap.R
import com.johan.evmap.api.ChargerPhoto
import com.ortiz.touchview.TouchImageView
import com.squareup.picasso.Callback
import com.squareup.picasso.Picasso
class GalleryAdapter(context: Context) :
class GalleryAdapter(
context: Context,
val itemClickListener: ItemClickListener? = null,
val detailView: Boolean = false,
val pageToLoad: Int? = null,
val loadedListener: (() -> Unit)? = null
) :
ListAdapter<ChargerPhoto, GalleryAdapter.ViewHolder>(ChargerPhotoDiffCallback()) {
class ViewHolder(val view: ImageView) : RecyclerView.ViewHolder(view)
val apikey = context.getString(R.string.goingelectric_key)
interface ItemClickListener {
fun onItemClick(view: View, position: Int)
}
val apikey = context.getString(R.string.goingelectric_key)
var loaded = false
@SuppressLint("ClickableViewAccessibility")
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.gallery_item, parent, false) as ImageView
val inflater = LayoutInflater.from(parent.context)
val view: ImageView
if (detailView) {
view = inflater.inflate(R.layout.gallery_item_fullscreen, parent, false) as ImageView
view.setOnTouchListener { view, event ->
var result = true
//can scroll horizontally checks if there's still a part of the image
//that can be scrolled until you reach the edge
if (event.pointerCount >= 2 || view.canScrollHorizontally(1) && view.canScrollHorizontally(
-1
)
) {
//multi-touch event
result = when (event.action) {
MotionEvent.ACTION_DOWN, MotionEvent.ACTION_MOVE -> {
// Disallow RecyclerView to intercept touch events.
parent.requestDisallowInterceptTouchEvent(true)
// Disable touch on view
false
}
MotionEvent.ACTION_UP -> {
// Allow RecyclerView to intercept touch events.
parent.requestDisallowInterceptTouchEvent(false)
true
}
else -> true
}
}
result
}
} else {
view = inflater.inflate(R.layout.gallery_item, parent, false) as ImageView
}
return ViewHolder(view)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
if (detailView) {
(holder.view as TouchImageView).resetZoom()
}
Picasso.get()
.load(
"https://api.goingelectric.de/chargepoints/photo/?key=${apikey}&id=${getItem(
position
).id}&height=${holder.view.height}"
"https://api.goingelectric.de/chargepoints/photo/?key=${apikey}" +
"&id=${getItem(position).id}" +
if (detailView) {
"&size=1000"
} else {
"&height=${holder.view.height}"
}
)
.into(holder.view)
.into(holder.view, object : Callback {
override fun onSuccess() {
if (!loaded && loadedListener != null && pageToLoad == position) {
holder.view.viewTreeObserver.addOnPreDrawListener(object :
ViewTreeObserver.OnPreDrawListener {
override fun onPreDraw(): Boolean {
holder.view.viewTreeObserver.removeOnPreDrawListener(this)
loadedListener.invoke()
return true
}
})
loaded = true
}
}
override fun onError(e: Exception?) {
if (!loaded && loadedListener != null && pageToLoad == position) {
loadedListener.invoke()
loaded = true
}
}
})
holder.view.transitionName = galleryTransitionName(position)
if (itemClickListener != null) {
holder.view.setOnClickListener {
itemClickListener.onItemClick(holder.view, position)
}
}
}
}
fun galleryTransitionName(position: Int) = "gallery_$position"
class ChargerPhotoDiffCallback : DiffUtil.ItemCallback<ChargerPhoto>() {
override fun areItemsTheSame(oldItem: ChargerPhoto, newItem: ChargerPhoto): Boolean {
return oldItem.id == newItem.id

View File

@@ -1,6 +1,7 @@
package com.johan.evmap.api
import android.content.Context
import android.os.Parcelable
import androidx.core.text.HtmlCompat
import com.johan.evmap.R
import com.johan.evmap.adapter.Equatable
@@ -144,7 +145,8 @@ data class Hours(
)
@JsonClass(generateAdapter = true)
data class ChargerPhoto(val id: String)
@Parcelize
data class ChargerPhoto(val id: String) : Parcelable
@JsonClass(generateAdapter = true)
data class ChargeLocationCluster(

View File

@@ -7,6 +7,7 @@ import android.widget.ImageView
import androidx.databinding.BindingAdapter
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import androidx.viewpager2.widget.ViewPager2
import com.google.android.material.floatingactionbutton.FloatingActionButton
import com.johan.evmap.R
@@ -39,6 +40,13 @@ fun <T> setRecyclerViewData(recyclerView: RecyclerView, items: List<T>?) {
}
}
@BindingAdapter("data")
fun <T> setRecyclerViewData(recyclerView: ViewPager2, items: List<T>?) {
if (recyclerView.adapter is ListAdapter<*, *>) {
(recyclerView.adapter as ListAdapter<T, *>).submitList(items)
}
}
@BindingAdapter("connectorIcon")
fun getConnectorItem(view: ImageView, type: String) {
view.setImageResource(