Some work on a better reply open animation

multisite
Floens 10 years ago
parent 4e67a47ad7
commit 2bd2de93a9
  1. 4
      Clover/app/src/main/java/org/floens/chan/core/presenter/ReplyPresenter.java
  2. 56
      Clover/app/src/main/java/org/floens/chan/ui/layout/ReplyLayout.java
  3. 2
      Clover/app/src/main/java/org/floens/chan/ui/layout/ThreadLayout.java
  4. 193
      Clover/app/src/main/java/org/floens/chan/ui/layout/ThreadListLayout.java
  5. 4
      Clover/app/src/main/java/org/floens/chan/utils/AndroidUtils.java
  6. BIN
      Clover/app/src/main/res/drawable-mdpi/partyhat.png

@ -125,6 +125,10 @@ public class ReplyPresenter implements ReplyManager.HttpCallback<ReplyHttpCall>,
closeAll();
}
public Page getPage() {
return page;
}
public boolean onBack() {
if (page == Page.LOADING) {
return true;

@ -17,6 +17,9 @@
*/
package org.floens.chan.ui.layout;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Bitmap;
import android.text.Editable;
@ -25,6 +28,7 @@ import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.DecelerateInterpolator;
import android.widget.CheckBox;
import android.widget.EditText;
import android.widget.ImageView;
@ -63,6 +67,8 @@ public class ReplyLayout extends LoadView implements View.OnClickListener, Anima
private View replyInputLayout;
private CaptchaLayout captchaLayout;
private ValueAnimator heightOffsetAnimator;
private boolean openingName;
private boolean blockSelectionChange = false;
private TextView message;
@ -105,7 +111,7 @@ public class ReplyLayout extends LoadView implements View.OnClickListener, Anima
protected void onFinishInflate() {
super.onFinishInflate();
setAnimateLayout(true, true);
// setAnimateLayout(true, true);
presenter = new ReplyPresenter(this);
@ -159,7 +165,25 @@ public class ReplyLayout extends LoadView implements View.OnClickListener, Anima
removeCallbacks(closeMessageRunnable);
}
@Override
public void open(final boolean open) {
int height = getMeasuredHeight();
animateHeightOffset(open ? 0 : height, open ? height : 0);
}
public int getHeightOffset() {
if (heightOffsetAnimator == null) {
return 0;
} else {
int animatedValue = (int) heightOffsetAnimator.getAnimatedValue();
return animatedValue - getHeight();
}
}
public boolean wrapHeight() {
return presenter.getPage() == ReplyPresenter.Page.INPUT;
}
/*@Override
public LayoutParams getLayoutParamsForView(View view) {
if (view == replyInputLayout) {
return new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
@ -167,7 +191,7 @@ public class ReplyLayout extends LoadView implements View.OnClickListener, Anima
// Captcha and the loadbar
return new LayoutParams(LayoutParams.MATCH_PARENT, getResources().getDimensionPixelSize(R.dimen.reply_height_loading));
}
}
}*/
@Override
public void onLayoutAnimationProgress(View view, boolean vertical, int from, int to, int value, float progress) {
@ -195,7 +219,7 @@ public class ReplyLayout extends LoadView implements View.OnClickListener, Anima
@Override
public void setPage(ReplyPresenter.Page page, boolean animate) {
setAnimateLayout(animate, true);
// setAnimateLayout(animate, true);
switch (page) {
case LOADING:
setView(null);
@ -210,6 +234,8 @@ public class ReplyLayout extends LoadView implements View.OnClickListener, Anima
setView(captchaLayout);
animateHeightOffset(getHeight(), ((View) getParent()).getHeight());
AndroidUtils.hideKeyboard(this);
break;
}
@ -403,6 +429,28 @@ public class ReplyLayout extends LoadView implements View.OnClickListener, Anima
return callback.getThread();
}
private void animateHeightOffset(int from, final int to) {
requestLayout();
heightOffsetAnimator = ValueAnimator.ofInt(from, to);
heightOffsetAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
((View) getParent()).invalidate();
}
});
heightOffsetAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
heightOffsetAnimator = null;
setVisibility(to != 0 ? VISIBLE : GONE);
}
});
heightOffsetAnimator.setInterpolator(new DecelerateInterpolator(2f));
heightOffsetAnimator.setDuration(300);
heightOffsetAnimator.start();
}
public interface ReplyLayoutCallback {
void highlightPostNo(int no);

@ -188,7 +188,7 @@ public class ThreadLayout extends CoordinatorLayout implements ThreadPresenter.T
}
@Override
public void replyLayoutOpen(boolean open) {
public void replyLayoutOpenChanged(boolean open) {
showReplyButton(!open);
}

@ -18,13 +18,17 @@
package org.floens.chan.ui.layout;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.support.annotation.NonNull;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.util.AttributeSet;
import android.view.KeyEvent;
import android.view.View;
import android.widget.FrameLayout;
import android.view.ViewGroup;
import android.widget.TextView;
import org.floens.chan.R;
@ -43,7 +47,9 @@ import org.floens.chan.ui.toolbar.Toolbar;
import org.floens.chan.ui.view.ThumbnailView;
import org.floens.chan.utils.AndroidUtils;
import org.floens.chan.utils.AnimationUtils;
import org.floens.chan.utils.Logger;
import java.util.Calendar;
import java.util.List;
import static org.floens.chan.utils.AndroidUtils.ROBOTO_MEDIUM;
@ -51,24 +57,30 @@ import static org.floens.chan.utils.AndroidUtils.dp;
import static org.floens.chan.utils.AndroidUtils.getAttrColor;
/**
* A layout that wraps around a {@link RecyclerView} to manage showing posts.
* A layout that wraps around a {@link RecyclerView}, and a {@link ReplyLayout} to manage showing posts.
*/
public class ThreadListLayout extends FrameLayout implements ReplyLayout.ReplyLayoutCallback {
public class ThreadListLayout extends ViewGroup implements ReplyLayout.ReplyLayoutCallback {
public static final int MAX_SMOOTH_SCROLL_DISTANCE = 20;
private RecyclerView recyclerView;
private ReplyLayout reply;
private TextView searchStatus;
private RecyclerView recyclerView;
private RecyclerView.LayoutManager layoutManager;
private PostAdapter postAdapter;
private ChanThread showingThread;
private ThreadListLayoutPresenterCallback callback;
private ThreadListLayoutCallback threadListLayoutCallback;
private boolean replyOpen;
private PostCellInterface.PostViewMode postViewMode;
private int spanCount = 2;
private int background;
private boolean replyOpen;
private boolean searchOpen;
private ChanThread showingThread;
private int background;
private int lastPostCount;
private int recyclerViewTopPadding;
@ -125,18 +137,6 @@ public class ThreadListLayout extends FrameLayout implements ReplyLayout.ReplyLa
searchStatus.getPaddingRight(), searchStatus.getPaddingBottom());
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int cardWidth = getResources().getDimensionPixelSize(R.dimen.grid_card_width);
spanCount = Math.max(1, Math.round(getMeasuredWidth() / cardWidth));
if (postViewMode == PostCellInterface.PostViewMode.CARD) {
((GridLayoutManager) layoutManager).setSpanCount(spanCount);
}
}
public void setPostViewMode(PostCellInterface.PostViewMode postViewMode) {
if (this.postViewMode != postViewMode) {
this.postViewMode = postViewMode;
@ -196,6 +196,8 @@ public class ThreadListLayout extends FrameLayout implements ReplyLayout.ReplyLa
((GridLayoutManager) layoutManager).scrollToPositionWithOffset(thread.loadable.listViewIndex, thread.loadable.listViewTop);
break;
}
party();
}
postAdapter.setThread(thread, filter);
@ -229,32 +231,32 @@ public class ThreadListLayout extends FrameLayout implements ReplyLayout.ReplyLa
}
public void openReply(boolean open) {
if (showingThread != null && replyOpen != open) {
if (showingThread != null && replyOpen != open && !searchOpen) {
this.replyOpen = open;
int height = AnimationUtils.animateHeight(reply, replyOpen, getWidth(), 500);
reply.setVisibility(VISIBLE);
reply.measure(
MeasureSpec.makeMeasureSpec(getWidth(), MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(getHeight(), MeasureSpec.AT_MOST));
reply.open(open);
/*int height = AnimationUtils.animateHeight(reply, replyOpen, getWidth(), 500);
if (open) {
reply.focusComment();
recyclerView.setPadding(recyclerView.getPaddingLeft(), recyclerViewTopPadding + height, recyclerView.getPaddingRight(), recyclerView.getPaddingBottom());
} else {
AndroidUtils.hideKeyboard(reply);
recyclerView.setPadding(recyclerView.getPaddingLeft(), recyclerViewTopPadding + topSpacing(), recyclerView.getPaddingRight(), recyclerView.getPaddingBottom());
}
threadListLayoutCallback.replyLayoutOpen(open);
}*/
threadListLayoutCallback.replyLayoutOpenChanged(open);
attachToolbarScroll(!(open || searchOpen));
}
}
public ReplyPresenter getReplyPresenter() {
return reply.getPresenter();
}
public void showError(String error) {
postAdapter.showError(error);
}
public void openSearch(boolean show) {
if (searchOpen != show) {
if (showingThread != null && searchOpen != show && !replyOpen) {
searchOpen = show;
int height = AnimationUtils.animateHeight(searchStatus, show);
@ -269,6 +271,14 @@ public class ThreadListLayout extends FrameLayout implements ReplyLayout.ReplyLa
}
}
public ReplyPresenter getReplyPresenter() {
return reply.getPresenter();
}
public void showError(String error) {
postAdapter.showError(error);
}
public void setSearchStatus(String query, boolean setEmptyText, boolean hideKeyboard) {
if (hideKeyboard) {
AndroidUtils.hideKeyboard(this);
@ -312,27 +322,13 @@ public class ThreadListLayout extends FrameLayout implements ReplyLayout.ReplyLa
}
public void cleanup() {
/*if (ChanBuild.DEVELOPER_MODE) {
Pin pin = ChanApplication.getWatchManager().findPinByLoadable(showingThread.loadable);
if (pin == null) {
for (Post post : showingThread.posts) {
if (post.comment instanceof SpannedString) {
SpannedString commentSpannable = (SpannedString) post.comment;
PostLinkable[] linkables = commentSpannable.getSpans(0, commentSpannable.length(), PostLinkable.class);
for (PostLinkable linkable : linkables) {
ChanApplication.getRefWatcher().watch(linkable, linkable.key + " " + linkable.value);
}
}
}
}
}*/
postAdapter.cleanup();
reply.cleanup();
openReply(false);
openSearch(false);
showingThread = null;
lastPostCount = 0;
noParty();
}
public List<Post> getDisplayingPosts() {
@ -418,6 +414,76 @@ public class ThreadListLayout extends FrameLayout implements ReplyLayout.ReplyLa
return showingThread;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
Logger.test("ThreadListLayout.onMeasure called");
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
if (widthMode != MeasureSpec.EXACTLY || heightMode != MeasureSpec.EXACTLY) {
throw new IllegalArgumentException("ThreadListLayout must be measured with MeasureSpec.EXACTLY");
}
setMeasuredDimension(widthSize, heightSize);
for (int i = 0, j = getChildCount(); i < j; i++) {
View child = getChildAt(i);
LayoutParams layoutParams = child.getLayoutParams();
if (child == recyclerView) {
child.measure(
MeasureSpec.makeMeasureSpec(widthSize, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(heightSize, MeasureSpec.EXACTLY)
);
} else if (child == searchStatus) {
child.measure(
MeasureSpec.makeMeasureSpec(widthSize, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(heightSize, MeasureSpec.AT_MOST)
);
} else if (child == reply) {
child.measure(
MeasureSpec.makeMeasureSpec(widthSize, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(heightSize, reply.wrapHeight() ? MeasureSpec.AT_MOST : MeasureSpec.EXACTLY)
);
} else {
throw new IllegalArgumentException("Unknown child " + child);
}
}
int cardWidth = getResources().getDimensionPixelSize(R.dimen.grid_card_width);
spanCount = Math.max(1, Math.round(getMeasuredWidth() / cardWidth));
if (postViewMode == PostCellInterface.PostViewMode.CARD) {
((GridLayoutManager) layoutManager).setSpanCount(spanCount);
}
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
Logger.test("ThreadListLayout.onLayout called");
for (int i = 0, j = getChildCount(); i < j; i++) {
View child = getChildAt(i);
LayoutParams layoutParams = child.getLayoutParams();
child.layout(0, 0, child.getMeasuredWidth(), child.getMeasuredHeight());
}
}
@Override
protected boolean drawChild(@NonNull Canvas canvas, @NonNull View child, long drawingTime) {
Logger.test("ThreadListLayout.drawChild called " + child);
if (child == reply) {
canvas.translate(0f, reply.getHeightOffset());
}
return super.drawChild(canvas, child, drawingTime);
}
private void attachToolbarScroll(boolean attach) {
Toolbar toolbar = threadListLayoutCallback.getToolbar();
if (toolbar != null && threadListLayoutCallback.collapseToolbar()) {
@ -459,6 +525,37 @@ public class ThreadListLayout extends FrameLayout implements ReplyLayout.ReplyLa
return -1;
}
private Bitmap hat;
private final RecyclerView.ItemDecoration PARTY = new RecyclerView.ItemDecoration() {
@Override
public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
if (hat == null) {
hat = BitmapFactory.decodeResource(getResources(), R.drawable.partyhat);
}
for (int i = 0, j = parent.getChildCount(); i < j; i++) {
View child = parent.getChildAt(i);
RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams();
int top = child.getBottom() + params.bottomMargin;
int left = child.getLeft() + params.leftMargin;
c.drawBitmap(hat, left - parent.getPaddingLeft() - dp(38), top - dp(125) - parent.getPaddingTop() + topSpacing(), null);
}
}
};
private void party() {
Calendar calendar = Calendar.getInstance();
if (showingThread.loadable.isCatalogMode() && calendar.get(Calendar.MONTH) == Calendar.OCTOBER && calendar.get(Calendar.DAY_OF_MONTH) == 1) {
recyclerView.addItemDecoration(PARTY);
}
}
private void noParty() {
recyclerView.removeItemDecoration(PARTY);
}
public interface ThreadListLayoutPresenterCallback {
void showThread(Loadable loadable);
@ -468,7 +565,7 @@ public class ThreadListLayout extends FrameLayout implements ReplyLayout.ReplyLa
}
public interface ThreadListLayoutCallback {
void replyLayoutOpen(boolean open);
void replyLayoutOpenChanged(boolean open);
Toolbar getToolbar();

@ -230,8 +230,8 @@ public class AndroidUtils {
}
public static void hideKeyboard(View view) {
InputMethodManager imm = (InputMethodManager) view.getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
InputMethodManager inputManager = (InputMethodManager) view.getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
inputManager.hideSoftInputFromWindow(view.getWindowToken(), 0);
}
public static String getReadableFileSize(long bytes, boolean si) {

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Loading…
Cancel
Save