mirror of
https://github.com/oxen-io/session-android.git
synced 2024-11-23 18:15:22 +00:00
Make the sticky date header only visible during scroll
// FREEBIE
This commit is contained in:
parent
2e16c6cf41
commit
d46d3b72c8
@ -1,5 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<set xmlns:android="http://schemas.android.com/apk/res/android" >
|
||||
<alpha android:duration="1"
|
||||
android:fromAlpha="0"
|
||||
android:toAlpha="1"/>
|
||||
|
||||
<translate
|
||||
android:duration="250"
|
||||
android:fromYDelta="-100%"
|
||||
|
@ -3,7 +3,24 @@
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="match_parent"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<TextView android:id="@+id/scroll_date_header"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:layout_gravity="center_horizontal|top"
|
||||
android:paddingLeft="6dp"
|
||||
android:paddingRight="6dp"
|
||||
android:paddingTop="3dp"
|
||||
android:paddingBottom="3dp"
|
||||
android:layout_marginTop="3dp"
|
||||
android:textColor="@color/white"
|
||||
android:background="?conversation_item_header_background"
|
||||
android:textSize="14sp"
|
||||
android:visibility="gone"
|
||||
tools:text="March 1, 2015" />
|
||||
|
||||
<android.support.v7.widget.RecyclerView
|
||||
android:id="@android:id/list"
|
||||
|
@ -124,7 +124,7 @@ public class ContactSelectionListFragment extends Fragment
|
||||
isMulti());
|
||||
selectedContacts = adapter.getSelectedContacts();
|
||||
recyclerView.setAdapter(adapter);
|
||||
recyclerView.addItemDecoration(new StickyHeaderDecoration(adapter, true));
|
||||
recyclerView.addItemDecoration(new StickyHeaderDecoration(adapter, true, true));
|
||||
this.getLoaderManager().initLoader(0, null, this);
|
||||
}
|
||||
|
||||
|
@ -105,13 +105,18 @@ public class ConversationAdapter <V extends View & BindableConversationItem>
|
||||
|
||||
|
||||
protected static class HeaderViewHolder extends RecyclerView.ViewHolder {
|
||||
private TextView textView;
|
||||
protected TextView textView;
|
||||
|
||||
public HeaderViewHolder(View itemView) {
|
||||
super(itemView);
|
||||
textView = ViewUtil.findById(itemView, R.id.text);
|
||||
}
|
||||
|
||||
public HeaderViewHolder(TextView textView) {
|
||||
super(textView);
|
||||
this.textView = textView;
|
||||
}
|
||||
|
||||
public void setText(CharSequence text) {
|
||||
textView.setText(text);
|
||||
}
|
||||
@ -283,6 +288,8 @@ public class ConversationAdapter <V extends View & BindableConversationItem>
|
||||
|
||||
@Override
|
||||
public long getHeaderId(int position) {
|
||||
if (!isActiveCursor()) return -1;
|
||||
|
||||
Cursor cursor = getCursorAtPositionOrThrow(position);
|
||||
MessageRecord record = getMessageRecord(cursor);
|
||||
|
||||
@ -300,10 +307,5 @@ public class ConversationAdapter <V extends View & BindableConversationItem>
|
||||
Cursor cursor = getCursorAtPositionOrThrow(position);
|
||||
viewHolder.setText(DateUtils.getRelativeDate(getContext(), locale, getMessageRecord(cursor).getDateReceived()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isActive() {
|
||||
return isActiveCursor();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -47,8 +47,10 @@ import android.view.ViewGroup;
|
||||
import android.view.Window;
|
||||
import android.view.animation.Animation;
|
||||
import android.view.animation.AnimationUtils;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import org.thoughtcrime.securesms.ConversationAdapter.HeaderViewHolder;
|
||||
import org.thoughtcrime.securesms.ConversationAdapter.ItemClickListener;
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
@ -94,6 +96,7 @@ public class ConversationFragment extends Fragment
|
||||
private View loadMoreView;
|
||||
private View composeDivider;
|
||||
private View scrollToBottomButton;
|
||||
private TextView scrollDateHeader;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle icicle) {
|
||||
@ -108,6 +111,7 @@ public class ConversationFragment extends Fragment
|
||||
list = ViewUtil.findById(view, android.R.id.list);
|
||||
composeDivider = ViewUtil.findById(view, R.id.compose_divider);
|
||||
scrollToBottomButton = ViewUtil.findById(view, R.id.scroll_to_bottom_button);
|
||||
scrollDateHeader = ViewUtil.findById(view, R.id.scroll_date_header);
|
||||
|
||||
scrollToBottomButton.setOnClickListener(new OnClickListener() {
|
||||
@Override
|
||||
@ -185,7 +189,7 @@ public class ConversationFragment extends Fragment
|
||||
if (this.recipients != null && this.threadId != -1) {
|
||||
ConversationAdapter adapter = new ConversationAdapter(getActivity(), masterSecret, locale, selectionClickListener, null, this.recipients);
|
||||
list.setAdapter(adapter);
|
||||
list.addItemDecoration(new StickyHeaderDecoration(adapter, false));
|
||||
list.addItemDecoration(new StickyHeaderDecoration(adapter, false, false));
|
||||
|
||||
getLoaderManager().restartLoader(0, Bundle.EMPTY, this);
|
||||
}
|
||||
@ -413,15 +417,18 @@ public class ConversationFragment extends Fragment
|
||||
|
||||
private class ConversationScrollListener extends OnScrollListener {
|
||||
|
||||
private final Animation scrollButtonInAnimation;
|
||||
private final Animation scrollButtonOutAnimation;
|
||||
private final Animation scrollButtonInAnimation;
|
||||
private final Animation scrollButtonOutAnimation;
|
||||
private final ConversationDateHeader conversationDateHeader;
|
||||
|
||||
private boolean wasAtBottom = true;
|
||||
private boolean wasAtZoomScrollHeight = false;
|
||||
private long lastPositionId = -1;
|
||||
|
||||
ConversationScrollListener(@NonNull Context context) {
|
||||
this.scrollButtonInAnimation = AnimationUtils.loadAnimation(context, R.anim.fade_scale_in);
|
||||
this.scrollButtonOutAnimation = AnimationUtils.loadAnimation(context, R.anim.fade_scale_out);
|
||||
this.conversationDateHeader = new ConversationDateHeader(context, scrollDateHeader);
|
||||
|
||||
this.scrollButtonInAnimation.setDuration(100);
|
||||
this.scrollButtonOutAnimation.setDuration(50);
|
||||
@ -431,6 +438,7 @@ public class ConversationFragment extends Fragment
|
||||
public void onScrolled(final RecyclerView rv, final int dx, final int dy) {
|
||||
boolean currentlyAtBottom = isAtBottom();
|
||||
boolean currentlyAtZoomScrollHeight = isAtZoomScrollHeight();
|
||||
int positionId = getHeaderPositionId();
|
||||
|
||||
if (currentlyAtBottom && !wasAtBottom) {
|
||||
ViewUtil.fadeOut(composeDivider, 50, View.INVISIBLE);
|
||||
@ -443,8 +451,22 @@ public class ConversationFragment extends Fragment
|
||||
ViewUtil.animateIn(scrollToBottomButton, scrollButtonInAnimation);
|
||||
}
|
||||
|
||||
if (positionId != lastPositionId) {
|
||||
bindScrollHeader(conversationDateHeader, positionId);
|
||||
}
|
||||
|
||||
wasAtBottom = currentlyAtBottom;
|
||||
wasAtZoomScrollHeight = currentlyAtZoomScrollHeight;
|
||||
lastPositionId = positionId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
|
||||
if (newState == RecyclerView.SCROLL_STATE_DRAGGING) {
|
||||
conversationDateHeader.show();
|
||||
} else if (newState == RecyclerView.SCROLL_STATE_IDLE) {
|
||||
conversationDateHeader.hide();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isAtBottom() {
|
||||
@ -460,6 +482,14 @@ public class ConversationFragment extends Fragment
|
||||
private boolean isAtZoomScrollHeight() {
|
||||
return ((LinearLayoutManager) list.getLayoutManager()).findFirstCompletelyVisibleItemPosition() > 4;
|
||||
}
|
||||
|
||||
private int getHeaderPositionId() {
|
||||
return ((LinearLayoutManager)list.getLayoutManager()).findLastVisibleItemPosition();
|
||||
}
|
||||
|
||||
private void bindScrollHeader(HeaderViewHolder headerViewHolder, int positionId) {
|
||||
((ConversationAdapter)list.getAdapter()).onBindHeaderViewHolder(headerViewHolder, positionId);
|
||||
}
|
||||
}
|
||||
|
||||
private class ConversationFragmentItemClickListener implements ItemClickListener {
|
||||
@ -553,4 +583,43 @@ public class ConversationFragment extends Fragment
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static class ConversationDateHeader extends HeaderViewHolder {
|
||||
|
||||
private final Animation animateIn;
|
||||
private final Animation animateOut;
|
||||
|
||||
private boolean pendingHide = false;
|
||||
|
||||
private ConversationDateHeader(Context context, TextView textView) {
|
||||
super(textView);
|
||||
this.animateIn = AnimationUtils.loadAnimation(context, R.anim.slide_from_top);
|
||||
this.animateOut = AnimationUtils.loadAnimation(context, R.anim.slide_to_top);
|
||||
|
||||
this.animateIn.setDuration(100);
|
||||
this.animateOut.setDuration(100);
|
||||
}
|
||||
|
||||
public void show() {
|
||||
if (pendingHide) {
|
||||
pendingHide = false;
|
||||
} else {
|
||||
ViewUtil.animateIn(textView, animateIn);
|
||||
}
|
||||
}
|
||||
|
||||
public void hide() {
|
||||
pendingHide = true;
|
||||
|
||||
textView.postDelayed(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (pendingHide) {
|
||||
pendingHide = false;
|
||||
ViewUtil.animateOut(textView, animateOut, View.GONE);
|
||||
}
|
||||
}
|
||||
}, 400);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -103,6 +103,8 @@ public class ContactSelectionListAdapter extends CursorRecyclerViewAdapter<ViewH
|
||||
|
||||
@Override
|
||||
public long getHeaderId(int i) {
|
||||
if (!isActiveCursor()) return -1;
|
||||
|
||||
return Util.hashCode(getHeaderString(i), isPush(i));
|
||||
}
|
||||
|
||||
@ -145,11 +147,6 @@ public class ContactSelectionListAdapter extends CursorRecyclerViewAdapter<ViewH
|
||||
return getHeaderString(position);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isActive() {
|
||||
return isActiveCursor();
|
||||
}
|
||||
|
||||
public Map<Long, String> getSelectedContacts() {
|
||||
return selectedContacts;
|
||||
}
|
||||
|
@ -7,7 +7,6 @@ import android.support.v4.view.ViewCompat;
|
||||
import android.support.v7.widget.LinearLayoutManager;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.support.v7.widget.RecyclerView.ViewHolder;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
@ -22,17 +21,21 @@ public class StickyHeaderDecoration extends RecyclerView.ItemDecoration {
|
||||
|
||||
private static final String TAG = StickyHeaderDecoration.class.getName();
|
||||
|
||||
private static final long NO_HEADER_ID = -1L;
|
||||
|
||||
private final Map<Long, ViewHolder> headerCache;
|
||||
private final StickyHeaderAdapter adapter;
|
||||
private final boolean renderInline;
|
||||
private boolean sticky;
|
||||
|
||||
/**
|
||||
* @param adapter the sticky header adapter to use
|
||||
*/
|
||||
public StickyHeaderDecoration(StickyHeaderAdapter adapter, boolean renderInline) {
|
||||
public StickyHeaderDecoration(StickyHeaderAdapter adapter, boolean renderInline, boolean sticky) {
|
||||
this.adapter = adapter;
|
||||
this.headerCache = new HashMap<>();
|
||||
this.renderInline = renderInline;
|
||||
this.sticky = sticky;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -42,9 +45,9 @@ public class StickyHeaderDecoration extends RecyclerView.ItemDecoration {
|
||||
public void getItemOffsets(Rect outRect, View view, RecyclerView parent,
|
||||
RecyclerView.State state)
|
||||
{
|
||||
int position = parent.getChildAdapterPosition(view);
|
||||
|
||||
int position = parent.getChildAdapterPosition(view);
|
||||
int headerHeight = 0;
|
||||
|
||||
if (position != RecyclerView.NO_POSITION && hasHeader(parent, adapter, position)) {
|
||||
View header = getHeader(parent, adapter, position).itemView;
|
||||
headerHeight = getHeaderHeightForLayout(header);
|
||||
@ -56,16 +59,14 @@ public class StickyHeaderDecoration extends RecyclerView.ItemDecoration {
|
||||
private boolean hasHeader(RecyclerView parent, StickyHeaderAdapter adapter, int adapterPos) {
|
||||
boolean isReverse = isReverseLayout(parent);
|
||||
|
||||
if (!adapter.isActive()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isReverse && adapterPos == ((RecyclerView.Adapter)adapter).getItemCount() - 1 || !isReverse && adapterPos == 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
int previous = adapterPos + (isReverse ? 1 : -1);
|
||||
return adapter.getHeaderId(adapterPos) != adapter.getHeaderId(previous);
|
||||
int previous = adapterPos + (isReverse ? 1 : -1);
|
||||
long headerId = adapter.getHeaderId(adapterPos);
|
||||
|
||||
return headerId != NO_HEADER_ID && (headerId != adapter.getHeaderId(previous));
|
||||
}
|
||||
|
||||
private ViewHolder getHeader(RecyclerView parent, StickyHeaderAdapter adapter, int position) {
|
||||
@ -109,7 +110,7 @@ public class StickyHeaderDecoration extends RecyclerView.ItemDecoration {
|
||||
|
||||
final int adapterPos = parent.getChildAdapterPosition(child);
|
||||
|
||||
if (adapterPos != RecyclerView.NO_POSITION && ((layoutPos == 0 && adapter.isActive()) || hasHeader(parent, adapter, adapterPos))) {
|
||||
if (adapterPos != RecyclerView.NO_POSITION && ((layoutPos == 0 && sticky) || hasHeader(parent, adapter, adapterPos))) {
|
||||
View header = getHeader(parent, adapter, adapterPos).itemView;
|
||||
c.save();
|
||||
final int left = child.getLeft();
|
||||
@ -146,7 +147,7 @@ public class StickyHeaderDecoration extends RecyclerView.ItemDecoration {
|
||||
}
|
||||
}
|
||||
|
||||
top = Math.max(0, top);
|
||||
if (sticky) top = Math.max(0, top);
|
||||
}
|
||||
|
||||
return top;
|
||||
@ -204,7 +205,5 @@ public class StickyHeaderDecoration extends RecyclerView.ItemDecoration {
|
||||
* @param position the header's item position
|
||||
*/
|
||||
void onBindHeaderViewHolder(T viewHolder, int position);
|
||||
|
||||
boolean isActive();
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user