Fix crossfade target aspect ratio.

This commit is contained in:
Alex Hart 2022-04-14 15:11:05 -03:00 committed by Greyson Parrelli
parent c3e7d6c74c
commit 2d60a88a75
16 changed files with 124 additions and 47 deletions

View file

@ -1244,7 +1244,7 @@ public class ConversationParentFragment extends Fragment
}
private void handleStoryRingClick() {
startActivity(StoryViewerActivity.createIntent(requireContext(), recipient.getId(), -1L, recipient.get().shouldHideStory(), null, null));
startActivity(StoryViewerActivity.createIntent(requireContext(), recipient.getId(), -1L, recipient.get().shouldHideStory(), null, null, null));
}
private void handleConversationSettings() {

View file

@ -140,7 +140,7 @@ final class RecipientDialogViewModel extends ViewModel {
if (storyViewState.getValue() == null || storyViewState.getValue() == StoryViewState.NONE) {
onMessageClicked(activity);
} else {
activity.startActivity(StoryViewerActivity.createIntent(activity, recipientDialogRepository.getRecipientId(), -1L, recipient.getValue().shouldHideStory(), null, null));
activity.startActivity(StoryViewerActivity.createIntent(activity, recipientDialogRepository.getRecipientId(), -1L, recipient.getValue().shouldHideStory(), null, null, null));
}
}
@ -176,7 +176,7 @@ final class RecipientDialogViewModel extends ViewModel {
if (storyViewState.getValue() == null || storyViewState.getValue() == StoryViewState.NONE) {
activity.startActivity(ConversationSettingsActivity.forRecipient(activity, recipientDialogRepository.getRecipientId()));
} else {
activity.startActivity(StoryViewerActivity.createIntent(activity, recipientDialogRepository.getRecipientId(), -1L, recipient.getValue().shouldHideStory(), null, null));
activity.startActivity(StoryViewerActivity.createIntent(activity, recipientDialogRepository.getRecipientId(), -1L, recipient.getValue().shouldHideStory(), null, null, null));
}
}

View file

@ -1,12 +0,0 @@
package org.thoughtcrime.securesms.stories.landing
/**
* Global Landing page payloads. Currently the only "pulse" we send out
* to ViewHolders is RESUMED.
*/
enum class LandingPayload {
/**
* Notifies view holders when the fragment is resumed.
*/
RESUMED
}

View file

@ -33,10 +33,6 @@ object MyStoriesItem {
private val badgeView: BadgeImageView = itemView.findViewById(R.id.badge)
override fun bind(model: Model) {
if (payload.contains(LandingPayload.RESUMED)) {
return
}
itemView.setOnClickListener { model.onClick() }
thumbnail.setOnClickListener { model.onClickThumbnail() }

View file

@ -80,7 +80,7 @@ class StoriesLandingFragment : DSLSettingsFragment(layoutId = R.layout.stories_l
override fun onResume() {
super.onResume()
adapter.notifyItemRangeChanged(0, adapter.itemCount, LandingPayload.RESUMED)
viewModel.isTransitioningToAnotherScreen = false
}
override fun bindAdapter(adapter: DSLSettingsAdapter) {
@ -114,7 +114,9 @@ class StoriesLandingFragment : DSLSettingsFragment(layoutId = R.layout.stories_l
.ifNecessary()
.withRationaleDialog(getString(R.string.ConversationActivity_to_capture_photos_and_video_allow_signal_access_to_the_camera), R.drawable.ic_camera_24)
.withPermanentDenialDialog(getString(R.string.ConversationActivity_signal_needs_the_camera_permission_to_take_photos_or_video))
.onAllGranted { startActivity(MediaSelectionActivity.camera(requireContext(), isStory = true)) }
.onAllGranted {
startActivityIfAble(MediaSelectionActivity.camera(requireContext(), isStory = true))
}
.onAnyDenied { Toast.makeText(requireContext(), R.string.ConversationActivity_signal_needs_camera_permissions_to_take_photos_or_video, Toast.LENGTH_LONG).show() }
.execute()
}
@ -148,7 +150,7 @@ class StoriesLandingFragment : DSLSettingsFragment(layoutId = R.layout.stories_l
customPref(
MyStoriesItem.Model(
onClick = {
startActivity(Intent(requireContext(), MyStoriesActivity::class.java))
startActivityIfAble(Intent(requireContext(), MyStoriesActivity::class.java))
},
onClickThumbnail = {
cameraFab.performClick()
@ -184,7 +186,7 @@ class StoriesLandingFragment : DSLSettingsFragment(layoutId = R.layout.stories_l
data = data,
onRowClick = { model, preview ->
if (model.data.storyRecipient.isMyStory) {
startActivity(Intent(requireContext(), MyStoriesActivity::class.java))
startActivityIfAble(Intent(requireContext(), MyStoriesActivity::class.java))
} else if (model.data.primaryStory.messageRecord.isOutgoing && model.data.primaryStory.messageRecord.isFailed) {
if (model.data.primaryStory.messageRecord.isIdentityMismatchFailure) {
SafetyNumberChangeDialog.show(requireContext(), childFragmentManager, model.data.primaryStory.messageRecord)
@ -197,13 +199,14 @@ class StoriesLandingFragment : DSLSettingsFragment(layoutId = R.layout.stories_l
val options = ActivityOptionsCompat.makeSceneTransitionAnimation(requireActivity(), preview, ViewCompat.getTransitionName(preview) ?: "")
val record = model.data.primaryStory.messageRecord as MmsMessageRecord
val blur = record.slideDeck.thumbnailSlide?.placeholderBlur
val (text: StoryTextPostModel?, image: Uri?) = if (record.storyType.isTextStory) {
StoryTextPostModel.parseFrom(record) to null
} else {
null to record.slideDeck.thumbnailSlide?.uri
}
startActivity(StoryViewerActivity.createIntent(requireContext(), model.data.storyRecipient.id, -1L, model.data.isHidden, text, image), options.toBundle())
startActivityIfAble(StoryViewerActivity.createIntent(requireContext(), model.data.storyRecipient.id, -1L, model.data.isHidden, text, image, blur), options.toBundle())
}
},
onForwardStory = {
@ -212,7 +215,7 @@ class StoriesLandingFragment : DSLSettingsFragment(layoutId = R.layout.stories_l
}
},
onGoToChat = {
startActivity(ConversationIntents.createBuilder(requireContext(), it.data.storyRecipient.id, -1L).build())
startActivityIfAble(ConversationIntents.createBuilder(requireContext(), it.data.storyRecipient.id, -1L).build())
},
onHideStory = {
if (!it.data.isHidden) {
@ -256,7 +259,7 @@ class StoriesLandingFragment : DSLSettingsFragment(layoutId = R.layout.stories_l
override fun onOptionsItemSelected(item: MenuItem): Boolean {
return if (item.itemId == R.id.action_settings) {
startActivity(StorySettingsActivity.getIntent(requireContext()))
startActivityIfAble(StorySettingsActivity.getIntent(requireContext()))
true
} else {
false
@ -266,4 +269,13 @@ class StoriesLandingFragment : DSLSettingsFragment(layoutId = R.layout.stories_l
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
Permissions.onRequestPermissionsResult(this, requestCode, permissions, grantResults)
}
private fun startActivityIfAble(intent: Intent, options: Bundle? = null) {
if (viewModel.isTransitioningToAnotherScreen) {
return
}
viewModel.isTransitioningToAnotherScreen = true
startActivity(intent, options)
}
}

View file

@ -107,7 +107,7 @@ object StoriesLandingItem {
presentDateOrStatus(model)
setUpClickListeners(model)
if (payload.contains(STATUS_CHANGE) || payload.contains(LandingPayload.RESUMED)) {
if (payload.contains(STATUS_CHANGE)) {
return
}
@ -223,11 +223,6 @@ object StoriesLandingItem {
private fun setUpClickListeners(model: Model) {
itemView.setOnClickListener {
if (!itemView.isClickable) {
return@setOnClickListener
}
itemView.isClickable = false
model.onRowClick(model, storyPreview)
}

View file

@ -15,6 +15,7 @@ class StoriesLandingViewModel(private val storiesLandingRepository: StoriesLandi
private val disposables = CompositeDisposable()
val state: LiveData<StoriesLandingState> = store.stateLiveData
var isTransitioningToAnotherScreen: Boolean = false
init {
disposables += storiesLandingRepository.getStories().subscribe { stories ->

View file

@ -93,6 +93,7 @@ class MyStoriesFragment : DSLSettingsFragment(
}
val record = it.distributionStory.messageRecord as MmsMessageRecord
val blur = record.slideDeck.thumbnailSlide?.placeholderBlur
val (text: StoryTextPostModel?, image: Uri?) = if (record.storyType.isTextStory) {
StoryTextPostModel.parseFrom(record) to null
} else {
@ -100,7 +101,7 @@ class MyStoriesFragment : DSLSettingsFragment(
}
val options = ActivityOptionsCompat.makeSceneTransitionAnimation(requireActivity(), preview, ViewCompat.getTransitionName(preview) ?: "")
startActivity(StoryViewerActivity.createIntent(requireContext(), recipient.id, conversationMessage.messageRecord.id, recipient.shouldHideStory(), text, image), options.toBundle())
startActivity(StoryViewerActivity.createIntent(requireContext(), recipient.id, conversationMessage.messageRecord.id, recipient.shouldHideStory(), text, image, blur), options.toBundle())
}
},
onLongClick = {

View file

@ -8,6 +8,7 @@ import android.os.Bundle
import androidx.appcompat.app.AppCompatDelegate
import org.thoughtcrime.securesms.PassphraseRequiredActivity
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.blurhash.BlurHash
import org.thoughtcrime.securesms.components.voice.VoiceNoteMediaController
import org.thoughtcrime.securesms.components.voice.VoiceNoteMediaControllerOwner
import org.thoughtcrime.securesms.recipients.RecipientId
@ -39,7 +40,8 @@ class StoryViewerActivity : PassphraseRequiredActivity(), VoiceNoteMediaControll
intent.getLongExtra(ARG_START_STORY_ID, -1L),
intent.getBooleanExtra(ARG_HIDDEN_STORIES, false),
intent.getParcelableExtra(ARG_CROSSFADE_TEXT_MODEL),
intent.getParcelableExtra(ARG_CROSSFADE_IMAGE_URI)
intent.getParcelableExtra(ARG_CROSSFADE_IMAGE_URI),
intent.getStringExtra(ARG_CROSSFADE_IMAGE_BLUR)
)
)
.commit()
@ -58,6 +60,7 @@ class StoryViewerActivity : PassphraseRequiredActivity(), VoiceNoteMediaControll
private const val ARG_HIDDEN_STORIES = "hidden_stories"
private const val ARG_CROSSFADE_TEXT_MODEL = "crossfade.text.model"
private const val ARG_CROSSFADE_IMAGE_URI = "crossfade.image.uri"
private const val ARG_CROSSFADE_IMAGE_BLUR = "crossfade.image.blur"
@JvmStatic
fun createIntent(
@ -66,7 +69,8 @@ class StoryViewerActivity : PassphraseRequiredActivity(), VoiceNoteMediaControll
storyId: Long = -1L,
onlyIncludeHiddenStories: Boolean = false,
storyThumbTextModel: StoryTextPostModel? = null,
storyThumbUri: Uri? = null
storyThumbUri: Uri? = null,
storyThumbBlur: BlurHash? = null
): Intent {
return Intent(context, StoryViewerActivity::class.java)
.putExtra(ARG_START_RECIPIENT_ID, recipientId)
@ -74,6 +78,7 @@ class StoryViewerActivity : PassphraseRequiredActivity(), VoiceNoteMediaControll
.putExtra(ARG_HIDDEN_STORIES, onlyIncludeHiddenStories)
.putExtra(ARG_CROSSFADE_TEXT_MODEL, storyThumbTextModel)
.putExtra(ARG_CROSSFADE_IMAGE_URI, storyThumbUri)
.putExtra(ARG_CROSSFADE_IMAGE_BLUR, storyThumbBlur?.hash)
}
}
}

View file

@ -8,6 +8,7 @@ import androidx.fragment.app.viewModels
import androidx.lifecycle.LiveDataReactiveStreams
import androidx.viewpager2.widget.ViewPager2
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.blurhash.BlurHash
import org.thoughtcrime.securesms.recipients.RecipientId
import org.thoughtcrime.securesms.stories.StoryTextPostModel
import org.thoughtcrime.securesms.stories.viewer.page.StoryViewerPageFragment
@ -23,7 +24,7 @@ class StoryViewerFragment : Fragment(R.layout.stories_viewer_fragment), StoryVie
private val viewModel: StoryViewerViewModel by viewModels(
factoryProducer = {
StoryViewerViewModel.Factory(storyRecipientId, onlyIncludeHiddenStories, storyThumbTextModel, storyThumbUri, StoryViewerRepository())
StoryViewerViewModel.Factory(storyRecipientId, onlyIncludeHiddenStories, storyThumbTextModel, storyThumbUri, storuThumbBlur, StoryViewerRepository())
}
)
@ -42,6 +43,9 @@ class StoryViewerFragment : Fragment(R.layout.stories_viewer_fragment), StoryVie
private val storyThumbUri: Uri?
get() = requireArguments().getParcelable(ARG_CROSSFADE_IMAGE_URI)
private val storuThumbBlur: BlurHash?
get() = requireArguments().getString(ARG_CROSSFADE_IMAGE_BLUR)?.let { BlurHash.parseOrNull(it) }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
storyPager = view.findViewById(R.id.story_item_pager)
@ -108,13 +112,15 @@ class StoryViewerFragment : Fragment(R.layout.stories_viewer_fragment), StoryVie
private const val ARG_HIDDEN_STORIES = "hidden_stories"
private const val ARG_CROSSFADE_TEXT_MODEL = "crossfade.text.model"
private const val ARG_CROSSFADE_IMAGE_URI = "crossfade.image.uri"
private const val ARG_CROSSFADE_IMAGE_BLUR = "crossfade.image.blur"
fun create(
storyRecipientId: RecipientId,
storyId: Long,
onlyIncludeHiddenStories: Boolean,
storyThumbTextModel: StoryTextPostModel? = null,
storyThumbUri: Uri? = null
storyThumbUri: Uri? = null,
storyThumbBlur: String? = null
): Fragment {
return StoryViewerFragment().apply {
arguments = Bundle().apply {
@ -123,6 +129,7 @@ class StoryViewerFragment : Fragment(R.layout.stories_viewer_fragment), StoryVie
putBoolean(ARG_HIDDEN_STORIES, onlyIncludeHiddenStories)
putParcelable(ARG_CROSSFADE_TEXT_MODEL, storyThumbTextModel)
putParcelable(ARG_CROSSFADE_IMAGE_URI, storyThumbUri)
putString(ARG_CROSSFADE_IMAGE_BLUR, storyThumbBlur)
}
}
}

View file

@ -1,6 +1,7 @@
package org.thoughtcrime.securesms.stories.viewer
import android.net.Uri
import org.thoughtcrime.securesms.blurhash.BlurHash
import org.thoughtcrime.securesms.recipients.RecipientId
import org.thoughtcrime.securesms.stories.StoryTextPostModel
@ -13,7 +14,7 @@ data class StoryViewerState(
) {
sealed class CrossfadeSource {
object None : CrossfadeSource()
class ImageUri(val imageUri: Uri) : CrossfadeSource()
class ImageUri(val imageUri: Uri, val imageBlur: BlurHash?) : CrossfadeSource()
class TextModel(val storyTextPostModel: StoryTextPostModel) : CrossfadeSource()
}

View file

@ -8,6 +8,7 @@ import androidx.lifecycle.ViewModelProvider
import io.reactivex.rxjava3.core.Flowable
import io.reactivex.rxjava3.disposables.CompositeDisposable
import io.reactivex.rxjava3.kotlin.plusAssign
import org.thoughtcrime.securesms.blurhash.BlurHash
import org.thoughtcrime.securesms.recipients.RecipientId
import org.thoughtcrime.securesms.stories.StoryTextPostModel
import org.thoughtcrime.securesms.util.rx.RxStore
@ -18,6 +19,7 @@ class StoryViewerViewModel(
private val onlyIncludeHiddenStories: Boolean,
storyThumbTextModel: StoryTextPostModel?,
storyThumbUri: Uri?,
storyThumbBlur: BlurHash?,
private val repository: StoryViewerRepository,
) : ViewModel() {
@ -25,7 +27,7 @@ class StoryViewerViewModel(
StoryViewerState(
crossfadeSource = when {
storyThumbTextModel != null -> StoryViewerState.CrossfadeSource.TextModel(storyThumbTextModel)
storyThumbUri != null -> StoryViewerState.CrossfadeSource.ImageUri(storyThumbUri)
storyThumbUri != null -> StoryViewerState.CrossfadeSource.ImageUri(storyThumbUri, storyThumbBlur)
else -> StoryViewerState.CrossfadeSource.None
}
)
@ -154,10 +156,11 @@ class StoryViewerViewModel(
private val onlyIncludeHiddenStories: Boolean,
private val storyThumbTextModel: StoryTextPostModel?,
private val storyThumbUri: Uri?,
private val storyThumbBlur: BlurHash?,
private val repository: StoryViewerRepository
) : ViewModelProvider.Factory {
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
return modelClass.cast(StoryViewerViewModel(startRecipientId, onlyIncludeHiddenStories, storyThumbTextModel, storyThumbUri, repository)) as T
return modelClass.cast(StoryViewerViewModel(startRecipientId, onlyIncludeHiddenStories, storyThumbTextModel, storyThumbUri, storyThumbBlur, repository)) as T
}
}
}

View file

@ -260,7 +260,7 @@ class StoryViewerPageFragment :
viewModel.setIsSelectedPage(true)
when (parentState.crossfadeSource) {
is StoryViewerState.CrossfadeSource.TextModel -> storyCrossfader.setSourceView(parentState.crossfadeSource.storyTextPostModel)
is StoryViewerState.CrossfadeSource.ImageUri -> storyCrossfader.setSourceView(parentState.crossfadeSource.imageUri)
is StoryViewerState.CrossfadeSource.ImageUri -> storyCrossfader.setSourceView(parentState.crossfadeSource.imageUri, parentState.crossfadeSource.imageBlur)
else -> onReadyToAnimate()
}
} else {

View file

@ -14,6 +14,7 @@ import com.bumptech.glide.request.target.Target
import org.signal.core.util.DimensionUnit
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.animation.transitions.CrossfaderTransition
import org.thoughtcrime.securesms.blurhash.BlurHash
import org.thoughtcrime.securesms.database.model.MmsMessageRecord
import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader
import org.thoughtcrime.securesms.mms.GlideApp
@ -35,10 +36,14 @@ class StoriesSharedElementCrossFaderView @JvmOverloads constructor(
}
private val sourceView: ImageView = findViewById(R.id.source_image)
private val sourceBlurView: ImageView = findViewById(R.id.source_image_blur)
private val targetView: ImageView = findViewById(R.id.target_image)
private val targetBlurView: ImageView = findViewById(R.id.target_image_blur)
private var isSourceReady: Boolean = false
private var isSourceBlurReady: Boolean = false
private var isTargetReady: Boolean = false
private var isTargetBlurReady: Boolean = false
private var latestSource: Any? = null
private var latestTarget: Any? = null
@ -64,9 +69,12 @@ class StoriesSharedElementCrossFaderView @JvmOverloads constructor(
.dontAnimate()
.centerCrop()
.into(sourceView)
GlideApp.with(sourceBlurView).clear(sourceBlurView)
isSourceBlurReady = true
}
fun setSourceView(uri: Uri) {
fun setSourceView(uri: Uri, blur: BlurHash?) {
if (latestSource == uri) {
return
}
@ -84,13 +92,31 @@ class StoriesSharedElementCrossFaderView @JvmOverloads constructor(
.dontAnimate()
.centerCrop()
.into(sourceView)
if (blur == null) {
GlideApp.with(sourceBlurView).clear(sourceBlurView)
isSourceBlurReady = true
} else {
GlideApp.with(sourceBlurView)
.load(blur)
.addListener(
OnReadyListener {
isSourceBlurReady = true
notifyIfReady()
}
)
.dontAnimate()
.centerCrop()
.into(sourceBlurView)
}
}
fun setTargetView(messageRecord: MmsMessageRecord): Boolean {
val thumbUri = messageRecord.slideDeck.thumbnailSlide?.uri
val thumbBlur: BlurHash? = messageRecord.slideDeck.thumbnailSlide?.placeholderBlur
when {
messageRecord.storyType.isTextStory -> setTargetView(StoryTextPostModel.parseFrom(messageRecord))
thumbUri != null -> setTargetView(thumbUri)
thumbUri != null -> setTargetView(thumbUri, thumbBlur)
else -> return false
}
@ -116,9 +142,12 @@ class StoriesSharedElementCrossFaderView @JvmOverloads constructor(
.placeholder(storyTextPostModel.getPlaceholder())
.centerCrop()
.into(targetView)
GlideApp.with(sourceBlurView).clear(sourceBlurView)
isTargetBlurReady = true
}
private fun setTargetView(uri: Uri) {
private fun setTargetView(uri: Uri, blur: BlurHash?) {
if (latestTarget == uri) {
return
}
@ -134,12 +163,29 @@ class StoriesSharedElementCrossFaderView @JvmOverloads constructor(
}
)
.dontAnimate()
.centerCrop()
.fitCenter()
.into(targetView)
if (blur == null) {
GlideApp.with(targetBlurView).clear(targetBlurView)
isTargetBlurReady = true
} else {
GlideApp.with(targetBlurView)
.load(blur)
.addListener(
OnReadyListener {
isTargetBlurReady = true
notifyIfReady()
}
)
.dontAnimate()
.centerCrop()
.into(targetBlurView)
}
}
private fun notifyIfReady() {
if (isSourceReady && isTargetReady) {
if (isSourceReady && isTargetReady && isSourceBlurReady && isTargetBlurReady) {
callback?.onReadyToAnimate()
}
}
@ -159,11 +205,15 @@ class StoriesSharedElementCrossFaderView @JvmOverloads constructor(
override fun onCrossfadeAnimationUpdated(progress: Float, reverse: Boolean) {
if (reverse) {
sourceView.alpha = progress
sourceBlurView.alpha = progress
targetView.alpha = 1f - progress
targetBlurView.alpha = 1f - progress
radius = CORNER_RADIUS_EVALUATOR.evaluate(progress, CORNER_RADIUS_END, CORNER_RADIUS_START)
} else {
sourceView.alpha = 1f - progress
sourceBlurView.alpha = 1f - progress
targetView.alpha = progress
targetBlurView.alpha = progress
radius = CORNER_RADIUS_EVALUATOR.evaluate(progress, CORNER_RADIUS_START, CORNER_RADIUS_END)
}
}
@ -172,7 +222,9 @@ class StoriesSharedElementCrossFaderView @JvmOverloads constructor(
alpha = 1f
sourceView.alpha = if (reverse) 0f else 1f
sourceBlurView.alpha = if (reverse) 0f else 1f
targetView.alpha = if (reverse) 1f else 0f
targetBlurView.alpha = if (reverse) 1f else 0f
radius = if (reverse) CORNER_RADIUS_END else CORNER_RADIUS_START

View file

@ -34,8 +34,8 @@
android:focusable="true"
android:theme="@style/Widget.Material3.FloatingActionButton.Secondary"
android:transitionName="camera_fab"
app:elevation="0dp"
app:backgroundTint="@color/signal_colorSecondaryContainer"
app:elevation="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:srcCompat="@drawable/ic_camera_outline_24"

View file

@ -12,6 +12,15 @@
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/source_image_blur"
android:layout_width="0dp"
android:layout_height="match_parent"
android:importantForAccessibility="no"
app:layout_constraintDimensionRatio="48:72"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<ImageView
android:id="@+id/source_image"
android:layout_width="0dp"
@ -21,6 +30,13 @@
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<ImageView
android:id="@+id/target_image_blur"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:alpha="0"
android:importantForAccessibility="no" />
<ImageView
android:id="@+id/target_image"
android:layout_width="match_parent"