kopia lustrzana https://github.com/ryukoposting/Signal-Android
Fix several beta issues with new slide animations.
rodzic
ced05fe579
commit
98fce53cf1
|
@ -20,7 +20,7 @@ class BodyBubbleLayoutTransition(bodyBubble: ConversationItemBodyBubble) : Layou
|
|||
|
||||
animator.duration = getAnimator(CHANGE_DISAPPEARING).duration
|
||||
animator.addUpdateListener {
|
||||
val parentRecycler: RecyclerView? = bodyBubble.parent.parent as? RecyclerView
|
||||
val parentRecycler: RecyclerView? = bodyBubble.parent?.parent as? RecyclerView
|
||||
|
||||
try {
|
||||
parentRecycler?.invalidate()
|
||||
|
|
|
@ -166,7 +166,7 @@ public class ConversationAdapter
|
|||
|
||||
@Override
|
||||
public int getItemViewType(int position) {
|
||||
if (hasHeader() && position == 0) {
|
||||
if (isTypingViewEnabled() && position == 0) {
|
||||
return MESSAGE_TYPE_HEADER;
|
||||
}
|
||||
|
||||
|
@ -365,7 +365,7 @@ public class ConversationAdapter
|
|||
}
|
||||
|
||||
public @Nullable ConversationMessage getItem(int position) {
|
||||
position = hasHeader() ? position - 1 : position;
|
||||
position = isTypingViewEnabled() ? position - 1 : position;
|
||||
|
||||
if (position == -1) {
|
||||
return null;
|
||||
|
@ -421,7 +421,7 @@ public class ConversationAdapter
|
|||
*/
|
||||
@MainThread
|
||||
int getAdapterPositionForMessagePosition(int messagePosition) {
|
||||
return hasHeader() ? messagePosition + 1 : messagePosition;
|
||||
return isTypingViewEnabled() ? messagePosition + 1 : messagePosition;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -602,7 +602,7 @@ public class ConversationAdapter
|
|||
}
|
||||
}
|
||||
|
||||
public boolean hasHeader() {
|
||||
public boolean isTypingViewEnabled() {
|
||||
return isTypingViewEnabled;
|
||||
}
|
||||
|
||||
|
@ -611,7 +611,7 @@ public class ConversationAdapter
|
|||
}
|
||||
|
||||
private boolean isHeaderPosition(int position) {
|
||||
return hasHeader() && position == 0;
|
||||
return isTypingViewEnabled() && position == 0;
|
||||
}
|
||||
|
||||
private boolean isFooterPosition(int position) {
|
||||
|
|
|
@ -5,7 +5,7 @@ import androidx.annotation.NonNull;
|
|||
/**
|
||||
* Represents metadata about a conversation.
|
||||
*/
|
||||
final class ConversationData {
|
||||
public final class ConversationData {
|
||||
private final long threadId;
|
||||
private final long lastSeen;
|
||||
private final int lastSeenPosition;
|
||||
|
|
|
@ -53,9 +53,7 @@ import androidx.core.app.ActivityOptionsCompat;
|
|||
import androidx.core.text.HtmlCompat;
|
||||
import androidx.core.view.ViewCompat;
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
import androidx.lifecycle.Observer;
|
||||
import androidx.lifecycle.Transformations;
|
||||
import androidx.lifecycle.ViewModelProviders;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
@ -86,7 +84,7 @@ import org.thoughtcrime.securesms.conversation.ConversationAdapter.ItemClickList
|
|||
import org.thoughtcrime.securesms.conversation.ConversationAdapter.StickyHeaderViewHolder;
|
||||
import org.thoughtcrime.securesms.conversation.colors.Colorizer;
|
||||
import org.thoughtcrime.securesms.conversation.colors.RecyclerViewColorizer;
|
||||
import org.thoughtcrime.securesms.conversation.mutiselect.MultiselectItemAnimator;
|
||||
import org.thoughtcrime.securesms.conversation.mutiselect.ConversationItemAnimator;
|
||||
import org.thoughtcrime.securesms.conversation.mutiselect.MultiselectItemDecoration;
|
||||
import org.thoughtcrime.securesms.conversation.mutiselect.MultiselectPart;
|
||||
import org.thoughtcrime.securesms.conversation.mutiselect.forward.MultiselectForwardFragment;
|
||||
|
@ -158,7 +156,6 @@ import org.thoughtcrime.securesms.util.Util;
|
|||
import org.thoughtcrime.securesms.util.ViewUtil;
|
||||
import org.thoughtcrime.securesms.util.WindowUtil;
|
||||
import org.thoughtcrime.securesms.util.concurrent.SimpleTask;
|
||||
import org.thoughtcrime.securesms.util.livedata.LiveDataUtil;
|
||||
import org.thoughtcrime.securesms.util.task.ProgressDialogAsyncTask;
|
||||
import org.thoughtcrime.securesms.util.views.AdaptiveActionsToolbar;
|
||||
import org.thoughtcrime.securesms.wallpaper.ChatWallpaper;
|
||||
|
@ -223,8 +220,6 @@ public class ConversationFragment extends LoggingFragment implements Multiselect
|
|||
private ConversationUpdateTick conversationUpdateTick;
|
||||
private MultiselectItemDecoration multiselectItemDecoration;
|
||||
|
||||
private boolean initialDataLoaded = false;
|
||||
|
||||
public static void prepare(@NonNull Context context) {
|
||||
FrameLayout parent = new FrameLayout(context);
|
||||
parent.setLayoutParams(new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.WRAP_CONTENT));
|
||||
|
@ -258,15 +253,15 @@ public class ConversationFragment extends LoggingFragment implements Multiselect
|
|||
toolbarShadow = requireActivity().findViewById(R.id.conversation_toolbar_shadow);
|
||||
reactionsShade = view.findViewById(R.id.reactions_shade);
|
||||
|
||||
final LinearLayoutManager layoutManager = new SmoothScrollingLinearLayoutManager(getActivity(), true);
|
||||
final MultiselectItemAnimator multiselectItemAnimator = new MultiselectItemAnimator(() -> {
|
||||
final LinearLayoutManager layoutManager = new SmoothScrollingLinearLayoutManager(getActivity(), true);
|
||||
final ConversationItemAnimator conversationItemAnimator = new ConversationItemAnimator(() -> {
|
||||
ConversationAdapter adapter = getListAdapter();
|
||||
if (adapter == null) {
|
||||
return false;
|
||||
} else {
|
||||
return Util.hasItems(adapter.getSelectedItems());
|
||||
}
|
||||
}, () -> !initialDataLoaded, () -> list.canScrollVertically(1) || list.canScrollVertically(-1));
|
||||
}, () -> conversationViewModel.shouldPlayMessageAnimations(), () -> list.canScrollVertically(1) || list.canScrollVertically(-1));
|
||||
multiselectItemDecoration = new MultiselectItemDecoration(requireContext(),
|
||||
() -> conversationViewModel.getWallpaper().getValue());
|
||||
|
||||
|
@ -276,7 +271,7 @@ public class ConversationFragment extends LoggingFragment implements Multiselect
|
|||
RecyclerViewColorizer recyclerViewColorizer = new RecyclerViewColorizer(list);
|
||||
|
||||
list.addItemDecoration(multiselectItemDecoration);
|
||||
list.setItemAnimator(multiselectItemAnimator);
|
||||
list.setItemAnimator(conversationItemAnimator);
|
||||
|
||||
getViewLifecycleOwner().getLifecycle().addObserver(multiselectItemDecoration);
|
||||
|
||||
|
@ -307,25 +302,12 @@ public class ConversationFragment extends LoggingFragment implements Multiselect
|
|||
this.messageCountsViewModel = ViewModelProviders.of(requireActivity()).get(MessageCountsViewModel.class);
|
||||
this.conversationViewModel = ViewModelProviders.of(requireActivity(), new ConversationViewModel.Factory()).get(ConversationViewModel.class);
|
||||
|
||||
MutableLiveData<Boolean> hasSubmittedNonEmptyList = new MutableLiveData<>(false);
|
||||
LiveData<Boolean> hasMessagesInThread = Transformations.map(conversationViewModel.getConversationMetadata(), c -> c.getThreadSize() > 0);
|
||||
|
||||
LiveData<Boolean> playAnimations = LiveDataUtil.combineLatest(hasSubmittedNonEmptyList, hasMessagesInThread, (a, b) -> {
|
||||
if (a && b) {
|
||||
return true;
|
||||
} else {
|
||||
return !b;
|
||||
}
|
||||
});
|
||||
|
||||
playAnimations.observe(getViewLifecycleOwner(), p -> initialDataLoaded = p);
|
||||
|
||||
conversationViewModel.getChatColors().observe(getViewLifecycleOwner(), recyclerViewColorizer::setChatColors);
|
||||
conversationViewModel.getMessages().observe(getViewLifecycleOwner(), messages -> {
|
||||
ConversationAdapter adapter = getListAdapter();
|
||||
if (adapter != null) {
|
||||
getListAdapter().submitList(messages, () -> {
|
||||
hasSubmittedNonEmptyList.postValue(!messages.isEmpty());
|
||||
list.post(() -> conversationViewModel.onMessagesCommitted(messages));
|
||||
});
|
||||
}
|
||||
});
|
||||
|
@ -391,7 +373,7 @@ public class ConversationFragment extends LoggingFragment implements Multiselect
|
|||
public void clearFocusedItem() {
|
||||
multiselectItemDecoration.setFocusedItem(null);
|
||||
list.invalidateItemDecorations();
|
||||
reactionsShade.setVisibility(View.GONE);
|
||||
reactionsShade.setVisibility(View.INVISIBLE);
|
||||
}
|
||||
|
||||
private void updateConversationItemTimestamps() {
|
||||
|
@ -1040,7 +1022,7 @@ public class ConversationFragment extends LoggingFragment implements Multiselect
|
|||
.submit();
|
||||
} else if (conversation.getMessageRequestData().isMessageRequestAccepted()) {
|
||||
snapToTopDataObserver.buildScrollPosition(conversation.shouldScrollToLastSeen() ? lastSeenPosition : lastScrolledPosition)
|
||||
.withOnPerformScroll((layoutManager, position) -> layoutManager.scrollToPositionWithOffset(position, list.getHeight() - (conversation.shouldScrollToLastSeen() ? lastSeenScrollOffset : 0)))
|
||||
.withOnPerformScroll((layoutManager, position) -> layoutManager.scrollToPositionWithOffset(position, (list.getHeight() + reactionsShade.getHeight()) - (conversation.shouldScrollToLastSeen() ? lastSeenScrollOffset : 0)))
|
||||
.withOnScrollRequestComplete(afterScroll)
|
||||
.submit();
|
||||
} else {
|
||||
|
@ -1064,7 +1046,7 @@ public class ConversationFragment extends LoggingFragment implements Multiselect
|
|||
}
|
||||
|
||||
private boolean isTypingIndicatorShowing() {
|
||||
return getListAdapter().hasHeader();
|
||||
return getListAdapter().isTypingViewEnabled();
|
||||
}
|
||||
|
||||
public void onSearchQueryUpdated(@Nullable String query) {
|
||||
|
|
|
@ -6,6 +6,7 @@ import androidx.annotation.MainThread;
|
|||
import androidx.annotation.NonNull;
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
import androidx.lifecycle.Observer;
|
||||
import androidx.lifecycle.Transformations;
|
||||
import androidx.lifecycle.ViewModel;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
|
@ -37,8 +38,10 @@ import org.thoughtcrime.securesms.recipients.Recipient;
|
|||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||
import org.thoughtcrime.securesms.util.DefaultValueLiveData;
|
||||
import org.thoughtcrime.securesms.util.SingleLiveEvent;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
import org.thoughtcrime.securesms.util.ViewUtil;
|
||||
import org.thoughtcrime.securesms.util.livedata.LiveDataUtil;
|
||||
import org.thoughtcrime.securesms.util.livedata.Store;
|
||||
import org.thoughtcrime.securesms.wallpaper.ChatWallpaper;
|
||||
import org.whispersystems.libsignal.util.Pair;
|
||||
import org.whispersystems.libsignal.util.guava.Optional;
|
||||
|
@ -76,6 +79,8 @@ public class ConversationViewModel extends ViewModel {
|
|||
private final MutableLiveData<Integer> toolbarBottom;
|
||||
private final MutableLiveData<Integer> inlinePlayerHeight;
|
||||
private final LiveData<Integer> conversationTopMargin;
|
||||
private final Store<ThreadAnimationState> threadAnimationStateStore;
|
||||
private final Observer<ThreadAnimationState> threadAnimationStateStoreDriver;
|
||||
|
||||
private final Map<GroupId, Set<Recipient>> sessionMemberCache = new HashMap<>();
|
||||
|
||||
|
@ -83,22 +88,23 @@ public class ConversationViewModel extends ViewModel {
|
|||
private int jumpToPosition;
|
||||
|
||||
private ConversationViewModel() {
|
||||
this.context = ApplicationDependencies.getApplication();
|
||||
this.mediaRepository = new MediaRepository();
|
||||
this.conversationRepository = new ConversationRepository();
|
||||
this.recentMedia = new MutableLiveData<>();
|
||||
this.threadId = new MutableLiveData<>();
|
||||
this.showScrollButtons = new MutableLiveData<>(false);
|
||||
this.hasUnreadMentions = new MutableLiveData<>(false);
|
||||
this.recipientId = new MutableLiveData<>();
|
||||
this.events = new SingleLiveEvent<>();
|
||||
this.pagingController = new ProxyPagingController<>();
|
||||
this.conversationObserver = pagingController::onDataInvalidated;
|
||||
this.messageUpdateObserver = pagingController::onDataItemChanged;
|
||||
this.messageInsertObserver = messageId -> pagingController.onDataItemInserted(messageId, 0);
|
||||
this.toolbarBottom = new MutableLiveData<>();
|
||||
this.inlinePlayerHeight = new MutableLiveData<>();
|
||||
this.conversationTopMargin = Transformations.distinctUntilChanged(LiveDataUtil.combineLatest(toolbarBottom, inlinePlayerHeight, Integer::sum));
|
||||
this.context = ApplicationDependencies.getApplication();
|
||||
this.mediaRepository = new MediaRepository();
|
||||
this.conversationRepository = new ConversationRepository();
|
||||
this.recentMedia = new MutableLiveData<>();
|
||||
this.threadId = new MutableLiveData<>();
|
||||
this.showScrollButtons = new MutableLiveData<>(false);
|
||||
this.hasUnreadMentions = new MutableLiveData<>(false);
|
||||
this.recipientId = new MutableLiveData<>();
|
||||
this.events = new SingleLiveEvent<>();
|
||||
this.pagingController = new ProxyPagingController<>();
|
||||
this.conversationObserver = pagingController::onDataInvalidated;
|
||||
this.messageUpdateObserver = pagingController::onDataItemChanged;
|
||||
this.messageInsertObserver = messageId -> pagingController.onDataItemInserted(messageId, 0);
|
||||
this.toolbarBottom = new MutableLiveData<>();
|
||||
this.inlinePlayerHeight = new MutableLiveData<>();
|
||||
this.conversationTopMargin = Transformations.distinctUntilChanged(LiveDataUtil.combineLatest(toolbarBottom, inlinePlayerHeight, Integer::sum));
|
||||
this.threadAnimationStateStore = new Store<>(new ThreadAnimationState(-1L, null, false));
|
||||
|
||||
LiveData<Recipient> recipientLiveData = LiveDataUtil.mapAsync(recipientId, Recipient::resolved);
|
||||
LiveData<ThreadAndRecipient> threadAndRecipient = LiveDataUtil.combineLatest(threadId, recipientLiveData, ThreadAndRecipient::new);
|
||||
|
@ -158,6 +164,41 @@ public class ConversationViewModel extends ViewModel {
|
|||
chatColors = LiveDataUtil.mapDistinct(Transformations.switchMap(recipientId,
|
||||
id -> Recipient.live(id).getLiveData()),
|
||||
Recipient::getChatColors);
|
||||
|
||||
threadAnimationStateStore.update(threadId, (id, state) -> {
|
||||
if (state.getThreadId() == id) {
|
||||
return state;
|
||||
} else {
|
||||
return new ThreadAnimationState(id, null, false);
|
||||
}
|
||||
});
|
||||
|
||||
threadAnimationStateStore.update(metadata, (m, state) -> {
|
||||
if (state.getThreadId() == m.getThreadId()) {
|
||||
return state.copy(state.getThreadId(), m, state.getHasCommittedNonEmptyMessageList());
|
||||
} else {
|
||||
return state.copy(m.getThreadId(), m, false);
|
||||
}
|
||||
});
|
||||
|
||||
this.threadAnimationStateStoreDriver = state -> {};
|
||||
threadAnimationStateStore.getStateLiveData().observeForever(threadAnimationStateStoreDriver);
|
||||
}
|
||||
|
||||
void onMessagesCommitted(@NonNull List<ConversationMessage> conversationMessages) {
|
||||
if (Util.hasItems(conversationMessages)) {
|
||||
threadAnimationStateStore.update(state -> {
|
||||
if (state.getThreadId() == conversationMessages.get(0).getMessageRecord().getThreadId()) {
|
||||
return state.copy(state.getThreadId(), state.getThreadMetadata(), true);
|
||||
} else {
|
||||
return state;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
boolean shouldPlayMessageAnimations() {
|
||||
return threadAnimationStateStore.getState().shouldPlayMessageAnimations();
|
||||
}
|
||||
|
||||
void setToolbarBottom(int bottom) {
|
||||
|
@ -301,6 +342,7 @@ public class ConversationViewModel extends ViewModel {
|
|||
@Override
|
||||
protected void onCleared() {
|
||||
super.onCleared();
|
||||
threadAnimationStateStore.getStateLiveData().removeObserver(threadAnimationStateStoreDriver);
|
||||
ApplicationDependencies.getDatabaseObserver().unregisterObserver(conversationObserver);
|
||||
ApplicationDependencies.getDatabaseObserver().unregisterObserver(messageUpdateObserver);
|
||||
ApplicationDependencies.getDatabaseObserver().unregisterObserver(messageInsertObserver);
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
package org.thoughtcrime.securesms.conversation
|
||||
|
||||
/**
|
||||
* Represents how conversation bubbles should animate at any given time.
|
||||
*/
|
||||
data class ThreadAnimationState constructor(
|
||||
val threadId: Long,
|
||||
val threadMetadata: ConversationData?,
|
||||
val hasCommittedNonEmptyMessageList: Boolean
|
||||
) {
|
||||
fun shouldPlayMessageAnimations(): Boolean {
|
||||
return when {
|
||||
threadId == -1L || threadMetadata == null -> false
|
||||
threadMetadata.threadSize == 0 -> true
|
||||
threadMetadata.threadSize > 0 && hasCommittedNonEmptyMessageList -> true
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
package org.thoughtcrime.securesms.conversation.mutiselect
|
||||
|
||||
import android.animation.ObjectAnimator
|
||||
import android.animation.Animator
|
||||
import android.animation.AnimatorSet
|
||||
import android.animation.ValueAnimator
|
||||
import androidx.core.animation.doOnEnd
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
|
@ -12,9 +13,9 @@ import org.thoughtcrime.securesms.conversation.ConversationAdapter
|
|||
*
|
||||
* Can be expanded upon in the future to animate other things, such as message sends.
|
||||
*/
|
||||
class MultiselectItemAnimator(
|
||||
class ConversationItemAnimator(
|
||||
private val isInMultiSelectMode: () -> Boolean,
|
||||
private val isLoadingInitialContent: () -> Boolean,
|
||||
private val shouldPlayMessageAnimations: () -> Boolean,
|
||||
private val isParentFilled: () -> Boolean
|
||||
) : RecyclerView.ItemAnimator() {
|
||||
|
||||
|
@ -23,14 +24,30 @@ class MultiselectItemAnimator(
|
|||
CHANGE
|
||||
}
|
||||
|
||||
private val pendingSlideAnimations: MutableSet<RecyclerView.ViewHolder> = mutableSetOf()
|
||||
private var pendingTypingViewSlideOut: RecyclerView.ViewHolder? = null
|
||||
private data class TweeningInfo(
|
||||
val startValue: Float,
|
||||
val endValue: Float
|
||||
) {
|
||||
fun lerp(progress: Float): Float {
|
||||
return startValue + progress * (endValue - startValue)
|
||||
}
|
||||
}
|
||||
|
||||
private val slideAnimations: MutableMap<RecyclerView.ViewHolder, ValueAnimator> = mutableMapOf()
|
||||
private data class AnimationInfo(
|
||||
val sharedAnimator: ValueAnimator,
|
||||
val tweeningInfo: TweeningInfo
|
||||
)
|
||||
|
||||
private val pendingSlideAnimations: MutableMap<RecyclerView.ViewHolder, TweeningInfo> = mutableMapOf()
|
||||
private val slideAnimations: MutableMap<RecyclerView.ViewHolder, AnimationInfo> = mutableMapOf()
|
||||
|
||||
override fun animateDisappearance(viewHolder: RecyclerView.ViewHolder, preLayoutInfo: ItemHolderInfo, postLayoutInfo: ItemHolderInfo?): Boolean {
|
||||
if (viewHolder is ConversationAdapter.HeaderViewHolder && pendingTypingViewSlideOut == null) {
|
||||
pendingTypingViewSlideOut = viewHolder
|
||||
if (viewHolder is ConversationAdapter.HeaderViewHolder &&
|
||||
!pendingSlideAnimations.containsKey(viewHolder) &&
|
||||
!slideAnimations.containsKey(viewHolder) &&
|
||||
shouldPlayMessageAnimations()
|
||||
) {
|
||||
pendingSlideAnimations[viewHolder] = TweeningInfo(0f, viewHolder.itemView.height.toFloat())
|
||||
dispatchAnimationStarted(viewHolder)
|
||||
return true
|
||||
}
|
||||
|
@ -49,7 +66,7 @@ class MultiselectItemAnimator(
|
|||
}
|
||||
|
||||
private fun animateSlide(viewHolder: RecyclerView.ViewHolder, preLayoutInfo: ItemHolderInfo?, postLayoutInfo: ItemHolderInfo, operation: Operation): Boolean {
|
||||
if (isInMultiSelectMode() || isLoadingInitialContent()) {
|
||||
if (isInMultiSelectMode() || !shouldPlayMessageAnimations()) {
|
||||
dispatchAnimationFinished(viewHolder)
|
||||
return false
|
||||
}
|
||||
|
@ -77,14 +94,14 @@ class MultiselectItemAnimator(
|
|||
|
||||
viewHolder.itemView.translationY = translationY
|
||||
|
||||
pendingSlideAnimations.add(viewHolder)
|
||||
pendingSlideAnimations[viewHolder] = TweeningInfo(translationY, 0f)
|
||||
dispatchAnimationStarted(viewHolder)
|
||||
return true
|
||||
}
|
||||
|
||||
override fun animatePersistence(viewHolder: RecyclerView.ViewHolder, preLayoutInfo: ItemHolderInfo, postLayoutInfo: ItemHolderInfo): Boolean {
|
||||
val isInMultiSelectMode = isInMultiSelectMode()
|
||||
return if (!isInMultiSelectMode) {
|
||||
return if (!isInMultiSelectMode && shouldPlayMessageAnimations()) {
|
||||
if (pendingSlideAnimations.contains(viewHolder) || slideAnimations.containsKey(viewHolder)) {
|
||||
dispatchAnimationFinished(viewHolder)
|
||||
false
|
||||
|
@ -107,51 +124,41 @@ class MultiselectItemAnimator(
|
|||
|
||||
override fun runPendingAnimations() {
|
||||
runPendingSlideAnimations()
|
||||
runPendingSlideOutAnimation()
|
||||
}
|
||||
|
||||
private fun runPendingSlideAnimations() {
|
||||
for (viewHolder in pendingSlideAnimations) {
|
||||
val animator = ObjectAnimator.ofFloat(viewHolder.itemView, "translationY", 0f)
|
||||
slideAnimations[viewHolder]?.cancel()
|
||||
slideAnimations[viewHolder] = animator
|
||||
val animators: MutableList<Animator> = mutableListOf()
|
||||
for ((viewHolder, tweeningInfo) in pendingSlideAnimations) {
|
||||
val animator = ValueAnimator.ofFloat(0f, 1f)
|
||||
slideAnimations[viewHolder] = AnimationInfo(animator, tweeningInfo)
|
||||
animator.duration = 150L
|
||||
animator.addUpdateListener {
|
||||
(viewHolder.itemView.parent as RecyclerView?)?.invalidate()
|
||||
if (viewHolder in slideAnimations) {
|
||||
viewHolder.itemView.translationY = tweeningInfo.lerp(it.animatedFraction)
|
||||
(viewHolder.itemView.parent as RecyclerView?)?.invalidate()
|
||||
}
|
||||
}
|
||||
animator.doOnEnd {
|
||||
viewHolder.itemView.translationY = 0f
|
||||
slideAnimations.remove(viewHolder)
|
||||
dispatchAnimationFinished(viewHolder)
|
||||
dispatchFinishedWhenDone()
|
||||
if (viewHolder in slideAnimations) {
|
||||
handleAnimationEnd(viewHolder)
|
||||
}
|
||||
}
|
||||
animator.start()
|
||||
animators.add(animator)
|
||||
}
|
||||
|
||||
AnimatorSet().apply {
|
||||
playTogether(animators)
|
||||
start()
|
||||
}
|
||||
|
||||
pendingSlideAnimations.clear()
|
||||
}
|
||||
|
||||
private fun runPendingSlideOutAnimation() {
|
||||
val viewHolder = pendingTypingViewSlideOut
|
||||
if (viewHolder != null) {
|
||||
pendingTypingViewSlideOut = null
|
||||
slideAnimations[viewHolder]?.cancel()
|
||||
|
||||
val animator = ObjectAnimator.ofFloat(viewHolder.itemView, "translationY", viewHolder.itemView.height.toFloat())
|
||||
|
||||
slideAnimations[viewHolder] = animator
|
||||
animator.duration = 150L
|
||||
animator.addUpdateListener {
|
||||
(viewHolder.itemView.parent as RecyclerView?)?.invalidate()
|
||||
}
|
||||
animator.doOnEnd {
|
||||
viewHolder.itemView.translationY = 0f
|
||||
slideAnimations.remove(viewHolder)
|
||||
dispatchAnimationFinished(viewHolder)
|
||||
dispatchFinishedWhenDone()
|
||||
}
|
||||
animator.start()
|
||||
}
|
||||
private fun handleAnimationEnd(viewHolder: RecyclerView.ViewHolder) {
|
||||
viewHolder.itemView.translationY = 0f
|
||||
slideAnimations.remove(viewHolder)
|
||||
dispatchAnimationFinished(viewHolder)
|
||||
dispatchFinishedWhenDone()
|
||||
}
|
||||
|
||||
override fun endAnimation(item: RecyclerView.ViewHolder) {
|
||||
|
@ -164,7 +171,7 @@ class MultiselectItemAnimator(
|
|||
}
|
||||
|
||||
override fun isRunning(): Boolean {
|
||||
return slideAnimations.values.any { it.isRunning }
|
||||
return slideAnimations.values.any { it.sharedAnimator.isRunning }
|
||||
}
|
||||
|
||||
override fun onAnimationFinished(viewHolder: RecyclerView.ViewHolder) {
|
||||
|
@ -173,11 +180,13 @@ class MultiselectItemAnimator(
|
|||
}
|
||||
|
||||
private fun endSlideAnimation(item: RecyclerView.ViewHolder) {
|
||||
slideAnimations[item]?.cancel()
|
||||
slideAnimations[item]?.sharedAnimator?.cancel()
|
||||
}
|
||||
|
||||
fun endSlideAnimations() {
|
||||
slideAnimations.values.forEach { it.cancel() }
|
||||
slideAnimations.values.map { it.sharedAnimator }.forEach {
|
||||
it.cancel()
|
||||
}
|
||||
}
|
||||
|
||||
private fun dispatchFinishedWhenDone() {
|
|
@ -12,7 +12,7 @@
|
|||
android:layout_height="0dp"
|
||||
android:background="@color/reactions_screen_shade_color"
|
||||
app:layout_constraintTop_toBottomOf="@android:id/list"
|
||||
android:visibility="gone" />
|
||||
android:visibility="invisible" />
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/video_container"
|
||||
|
|
Ładowanie…
Reference in New Issue