Add collapsing of the toolbar on scroll

multisite
Floens 10 years ago
parent bebfa928ce
commit 82a64ddb96
  1. 19
      Clover/app/src/main/java/org/floens/chan/controller/ControllerLogic.java
  2. 14
      Clover/app/src/main/java/org/floens/chan/controller/NavigationController.java
  3. 2
      Clover/app/src/main/java/org/floens/chan/controller/PopControllerTransition.java
  4. 2
      Clover/app/src/main/java/org/floens/chan/controller/PushControllerTransition.java
  5. 2
      Clover/app/src/main/java/org/floens/chan/core/presenter/ThreadPresenter.java
  6. 12
      Clover/app/src/main/java/org/floens/chan/ui/controller/ThreadController.java
  7. 20
      Clover/app/src/main/java/org/floens/chan/ui/layout/ThreadLayout.java
  8. 88
      Clover/app/src/main/java/org/floens/chan/ui/layout/ThreadListLayout.java
  9. 1
      Clover/app/src/main/java/org/floens/chan/ui/toolbar/NavigationItem.java
  10. 59
      Clover/app/src/main/java/org/floens/chan/ui/toolbar/Toolbar.java
  11. 5
      Clover/app/src/main/java/org/floens/chan/utils/AndroidUtils.java
  12. 10
      Clover/app/src/main/res/layout/controller_navigation_drawer.xml
  13. 10
      Clover/app/src/main/res/layout/controller_navigation_image_viewer.xml

@ -23,14 +23,21 @@ import org.floens.chan.utils.AndroidUtils;
public class ControllerLogic {
public static void attach(Controller controller, ViewGroup view, boolean over) {
ViewGroup.LayoutParams params = controller.view.getLayoutParams();
if (params == null) {
params = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
} else {
params.width = ViewGroup.LayoutParams.MATCH_PARENT;
params.height = ViewGroup.LayoutParams.MATCH_PARENT;
}
controller.view.setLayoutParams(params);
if (over) {
view.addView(controller.view,
new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)
);
view.addView(controller.view, controller.view.getLayoutParams());
} else {
view.addView(controller.view, 0,
new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)
);
view.addView(controller.view, 0, controller.view.getLayoutParams());
}
}

@ -80,6 +80,8 @@ public abstract class NavigationController extends Controller implements Control
toolbar.setNavigationItem(false, true, to.navigationItem);
}
updateToolbarCollapse(to, controllerTransition != null);
controllerPushed(to);
return true;
@ -128,6 +130,8 @@ public abstract class NavigationController extends Controller implements Control
}
if (to != null) {
updateToolbarCollapse(to, controllerTransition != null);
controllerPopped(to);
}
@ -224,4 +228,14 @@ public abstract class NavigationController extends Controller implements Control
@Override
public void onSearchEntered(String entered) {
}
private void updateToolbarCollapse(Controller controller, boolean animate) {
if (!controller.navigationItem.collapseToolbar) {
FrameLayout.LayoutParams toViewParams = (FrameLayout.LayoutParams) controller.view.getLayoutParams();
toViewParams.topMargin = toolbar.getToolbarHeight();
controller.view.setLayoutParams(toViewParams);
}
toolbar.processScrollCollapse(Toolbar.TOOLBAR_COLLAPSE_SHOW, animate);
}
}

@ -36,7 +36,7 @@ public class PopControllerTransition extends ControllerTransition {
toAlpha.setInterpolator(new DecelerateInterpolator()); // new PathInterpolator(0f, 0f, 0.2f, 1f)
toAlpha.setDuration(250);
Animator fromY = ObjectAnimator.ofFloat(from.view, View.Y, 0f, from.view.getHeight() * 0.05f);
Animator fromY = ObjectAnimator.ofFloat(from.view, View.TRANSLATION_Y, 0f, from.view.getHeight() * 0.05f);
fromY.setInterpolator(new AccelerateInterpolator(2.5f));
fromY.setDuration(250);

@ -41,7 +41,7 @@ public class PushControllerTransition extends ControllerTransition {
toAlpha.setDuration(200);
toAlpha.setInterpolator(new DecelerateInterpolator(2f));
Animator toY = ObjectAnimator.ofFloat(to.view, View.Y, to.view.getHeight() * 0.08f, 0f);
Animator toY = ObjectAnimator.ofFloat(to.view, View.TRANSLATION_Y, to.view.getHeight() * 0.08f, 0f);
toY.setDuration(350);
toY.setInterpolator(new DecelerateInterpolator(2.5f));

@ -54,7 +54,7 @@ import java.util.List;
import static org.floens.chan.utils.AndroidUtils.getString;
public class ThreadPresenter implements ChanLoader.ChanLoaderCallback, PostAdapter.PostAdapterCallback, PostCellInterface.PostCellCallback, ThreadStatusCell.Callback, ThreadListLayout.ThreadListLayoutCallback {
public class ThreadPresenter implements ChanLoader.ChanLoaderCallback, PostAdapter.PostAdapterCallback, PostCellInterface.PostCellCallback, ThreadStatusCell.Callback, ThreadListLayout.ThreadListLayoutPresenterCallback {
private static final int POST_OPTION_QUOTE = 0;
private static final int POST_OPTION_QUOTE_TEXT = 1;
private static final int POST_OPTION_INFO = 2;

@ -33,6 +33,7 @@ import org.floens.chan.core.model.Loadable;
import org.floens.chan.core.model.PostImage;
import org.floens.chan.ui.helper.RefreshUIMessage;
import org.floens.chan.ui.layout.ThreadLayout;
import org.floens.chan.ui.toolbar.Toolbar;
import org.floens.chan.ui.view.ThumbnailView;
import org.floens.chan.utils.Logger;
@ -40,6 +41,8 @@ import java.util.List;
import de.greenrobot.event.EventBus;
import static org.floens.chan.utils.AndroidUtils.dp;
public abstract class ThreadController extends Controller implements ThreadLayout.ThreadLayoutCallback, ImageViewerController.PreviewCallback, RootNavigationController.DrawerCallback, SwipeRefreshLayout.OnRefreshListener, RootNavigationController.ToolbarSearchCallback, NfcAdapter.CreateNdefMessageCallback {
private static final String TAG = "ThreadController";
@ -56,6 +59,8 @@ public abstract class ThreadController extends Controller implements ThreadLayou
EventBus.getDefault().register(this);
navigationItem.collapseToolbar = true;
threadLayout = (ThreadLayout) LayoutInflater.from(context).inflate(R.layout.layout_thread, null);
threadLayout.setCallback(this);
@ -68,6 +73,8 @@ public abstract class ThreadController extends Controller implements ThreadLayou
swipeRefreshLayout.addView(threadLayout);
swipeRefreshLayout.setOnRefreshListener(this);
int toolbarHeight = navigationController.toolbar.getToolbarHeight();
swipeRefreshLayout.setProgressViewOffset(false, toolbarHeight - dp(40), toolbarHeight + dp(64 - 40));
view = swipeRefreshLayout;
}
@ -177,6 +184,11 @@ public abstract class ThreadController extends Controller implements ThreadLayou
swipeRefreshLayout.setRefreshing(false);
}
@Override
public Toolbar getToolbar() {
return navigationController.toolbar;
}
@Override
public void onSearchVisibilityChanged(boolean visible) {
threadLayout.getPresenter().onSearchVisibilityChanged(visible);

@ -58,6 +58,7 @@ import org.floens.chan.core.settings.ChanSettings;
import org.floens.chan.ui.adapter.PostsFilter;
import org.floens.chan.ui.cell.PostCellInterface;
import org.floens.chan.ui.helper.PostPopupHelper;
import org.floens.chan.ui.toolbar.Toolbar;
import org.floens.chan.ui.view.LoadView;
import org.floens.chan.ui.view.ThumbnailView;
import org.floens.chan.utils.AndroidUtils;
@ -72,7 +73,7 @@ import static org.floens.chan.utils.AndroidUtils.getString;
/**
* Wrapper around ThreadListLayout, so that it cleanly manages between loadbar and listview.
*/
public class ThreadLayout extends CoordinatorLayout implements ThreadPresenter.ThreadPresenterCallback, PostPopupHelper.PostPopupHelperCallback, View.OnClickListener, ThreadListLayout.ReplyLayoutStateCallback {
public class ThreadLayout extends CoordinatorLayout implements ThreadPresenter.ThreadPresenterCallback, PostPopupHelper.PostPopupHelperCallback, View.OnClickListener, ThreadListLayout.ThreadListLayoutCallback {
private enum Visible {
LOADING,
THREAD,
@ -114,9 +115,9 @@ public class ThreadLayout extends CoordinatorLayout implements ThreadPresenter.T
init();
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
public void setCallback(ThreadLayoutCallback callback) {
this.callback = callback;
presenter = new ThreadPresenter(this);
loadView = (LoadView) findViewById(R.id.loadview);
@ -159,10 +160,6 @@ public class ThreadLayout extends CoordinatorLayout implements ThreadPresenter.T
return threadListLayout.onBack();
}
public void setCallback(ThreadLayoutCallback callback) {
this.callback = callback;
}
public ThreadPresenter getPresenter() {
return presenter;
}
@ -181,6 +178,11 @@ public class ThreadLayout extends CoordinatorLayout implements ThreadPresenter.T
showReplyButton(!open);
}
@Override
public Toolbar getToolbar() {
return callback.getToolbar();
}
@Override
public void showPosts(ChanThread thread, PostsFilter filter) {
threadListLayout.showPosts(thread, filter, visible != Visible.THREAD);
@ -492,5 +494,7 @@ public class ThreadLayout extends CoordinatorLayout implements ThreadPresenter.T
void presentRepliesController(Controller controller);
void hideSwipeRefreshLayout();
Toolbar getToolbar();
}
}

@ -37,6 +37,7 @@ import org.floens.chan.ui.adapter.PostsFilter;
import org.floens.chan.ui.cell.PostCell;
import org.floens.chan.ui.cell.PostCellInterface;
import org.floens.chan.ui.cell.ThreadStatusCell;
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;
@ -59,12 +60,15 @@ public class ThreadListLayout extends LinearLayout implements ReplyLayout.ReplyL
private RecyclerView.LayoutManager layoutManager;
private PostAdapter postAdapter;
private ChanThread showingThread;
private ThreadListLayoutCallback callback;
private ReplyLayoutStateCallback replyLayoutStateCallback;
private ThreadListLayoutPresenterCallback callback;
private ThreadListLayoutCallback threadListLayoutCallback;
private boolean replyOpen;
private PostCellInterface.PostViewMode postViewMode;
private int spanCount = 2;
private int background;
private int toolbarSpacing;
private Toolbar toolbar;
private boolean searchOpen;
public ThreadListLayout(Context context, AttributeSet attrs) {
super(context, attrs);
@ -84,10 +88,10 @@ public class ThreadListLayout extends LinearLayout implements ReplyLayout.ReplyL
}
public void setCallbacks(PostAdapter.PostAdapterCallback postAdapterCallback, PostCell.PostCellCallback postCellCallback,
ThreadStatusCell.Callback statusCellCallback, ThreadListLayoutCallback callback,
ReplyLayoutStateCallback replyLayoutStateCallback) {
ThreadStatusCell.Callback statusCellCallback, ThreadListLayoutPresenterCallback callback,
ThreadListLayoutCallback threadListLayoutCallback) {
this.callback = callback;
this.replyLayoutStateCallback = replyLayoutStateCallback;
this.threadListLayoutCallback = threadListLayoutCallback;
postAdapter = new PostAdapter(recyclerView, postAdapterCallback, postCellCallback, statusCellCallback);
recyclerView.setAdapter(postAdapter);
@ -96,17 +100,21 @@ public class ThreadListLayout extends LinearLayout implements ReplyLayout.ReplyL
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
// onScrolled can be called after cleanup()
if (showingThread != null) {
switch (postViewMode) {
case LIST:
showingThread.loadable.listViewIndex = Math.max(0, getTopAdapterPosition());
break;
case CARD:
showingThread.loadable.listViewIndex = Math.max(0, getTopAdapterPosition());
break;
}
int index = Math.max(0, getTopAdapterPosition());
int top = recyclerView.getLayoutManager().getChildAt(0).getTop();
showingThread.loadable.listViewIndex = index;
// showingThread.loadable.listViewTop = top;
}
}
});
toolbar = threadListLayoutCallback.getToolbar();
attachToolbarScroll(true);
int toolbarHeight = toolbar.getToolbarHeight();
reply.setPadding(0, toolbarHeight, 0, 0);
searchStatus.setPadding(searchStatus.getPaddingLeft(), searchStatus.getPaddingTop() + toolbarHeight,
searchStatus.getPaddingRight(), searchStatus.getPaddingBottom());
}
@Override
@ -132,10 +140,11 @@ public class ThreadListLayout extends LinearLayout implements ReplyLayout.ReplyL
layoutManager = null;
int toolbarHeight = toolbar.getToolbarHeight();
switch (postViewMode) {
case LIST:
LinearLayoutManager linearLayoutManager = new LinearLayoutManager(getContext());
recyclerView.setPadding(0, 0, 0, 0);
recyclerView.setPadding(0, toolbarHeight, 0, 0);
recyclerView.setLayoutManager(linearLayoutManager);
layoutManager = linearLayoutManager;
@ -148,7 +157,7 @@ public class ThreadListLayout extends LinearLayout implements ReplyLayout.ReplyL
case CARD:
GridLayoutManager gridLayoutManager = new GridLayoutManager(null, spanCount, GridLayoutManager.VERTICAL, false);
// The cards have a 4dp padding, this way there is always 8dp between the edges
recyclerView.setPadding(dp(4), dp(4), dp(4), dp(4));
recyclerView.setPadding(dp(4), dp(4) + toolbarHeight, dp(4), dp(4));
recyclerView.setLayoutManager(gridLayoutManager);
layoutManager = gridLayoutManager;
@ -177,10 +186,10 @@ public class ThreadListLayout extends LinearLayout implements ReplyLayout.ReplyL
switch (postViewMode) {
case LIST:
((LinearLayoutManager) layoutManager).scrollToPositionWithOffset(thread.loadable.listViewIndex, 0);
((LinearLayoutManager) layoutManager).scrollToPositionWithOffset(thread.loadable.listViewIndex, thread.loadable.listViewTop);
break;
case CARD:
((GridLayoutManager) layoutManager).scrollToPositionWithOffset(thread.loadable.listViewIndex, 0);
((GridLayoutManager) layoutManager).scrollToPositionWithOffset(thread.loadable.listViewIndex, thread.loadable.listViewTop);
break;
}
} else {
@ -220,7 +229,9 @@ public class ThreadListLayout extends LinearLayout implements ReplyLayout.ReplyL
} else {
AndroidUtils.hideKeyboard(reply);
}
replyLayoutStateCallback.replyLayoutOpen(open);
threadListLayoutCallback.replyLayoutOpen(open);
attachToolbarScroll(!(open || searchOpen));
}
}
@ -233,10 +244,15 @@ public class ThreadListLayout extends LinearLayout implements ReplyLayout.ReplyL
}
public void showSearch(boolean show) {
AnimationUtils.animateHeight(searchStatus, show);
if (searchOpen != show) {
searchOpen = show;
AnimationUtils.animateHeight(searchStatus, show);
if (show) {
searchStatus.setText(R.string.search_empty);
if (show) {
searchStatus.setText(R.string.search_empty);
}
attachToolbarScroll(!(show || replyOpen));
}
}
@ -261,17 +277,18 @@ public class ThreadListLayout extends LinearLayout implements ReplyLayout.ReplyL
return true;
}
int toolbarHeight = toolbar.getToolbarHeight();
switch (postViewMode) {
case LIST:
if (getTopAdapterPosition() == 0) {
View top = layoutManager.findViewByPosition(0);
return top.getTop() != 0;
return top.getTop() != toolbarHeight;
}
break;
case CARD:
if (getTopAdapterPosition() == 0) {
View top = layoutManager.findViewByPosition(0);
return top.getTop() != dp(8); // 4dp for the cards, 4dp for this layout
return top.getTop() != dp(8) + toolbarHeight; // 4dp for the cards, 4dp for this layout
}
break;
}
@ -383,6 +400,15 @@ public class ThreadListLayout extends LinearLayout implements ReplyLayout.ReplyL
callback.requestNewPostLoad();
}
private void attachToolbarScroll(boolean attach) {
if (attach) {
toolbar.attachRecyclerViewScrollStateListener(recyclerView);
} else {
toolbar.detachRecyclerViewScrollStateListener(recyclerView);
toolbar.setCollapse(Toolbar.TOOLBAR_COLLAPSE_SHOW, true);
}
}
private int getTopAdapterPosition() {
switch (postViewMode) {
case LIST:
@ -393,13 +419,25 @@ public class ThreadListLayout extends LinearLayout implements ReplyLayout.ReplyL
return -1;
}
public interface ThreadListLayoutCallback {
private int getCompleteTopAdapterPosition() {
switch (postViewMode) {
case LIST:
return ((LinearLayoutManager) layoutManager).findFirstCompletelyVisibleItemPosition();
case CARD:
return ((GridLayoutManager) layoutManager).findFirstCompletelyVisibleItemPosition();
}
return -1;
}
public interface ThreadListLayoutPresenterCallback {
void showThread(Loadable loadable);
void requestNewPostLoad();
}
public interface ReplyLayoutStateCallback {
public interface ThreadListLayoutCallback {
void replyLayoutOpen(boolean open);
Toolbar getToolbar();
}
}

@ -36,6 +36,7 @@ public class NavigationItem {
public FloatingMenu middleMenu;
public View rightView;
public boolean hasDrawer = false;
public boolean collapseToolbar = false;
boolean search = false;
String searchText;

@ -26,6 +26,7 @@ import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.support.v7.widget.RecyclerView;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.Gravity;
@ -53,6 +54,29 @@ import static org.floens.chan.utils.AndroidUtils.getAttrColor;
import static org.floens.chan.utils.AndroidUtils.setRoundItemBackground;
public class Toolbar extends LinearLayout implements View.OnClickListener, LoadView.Listener {
public static final int TOOLBAR_COLLAPSE_HIDE = 1000000;
public static final int TOOLBAR_COLLAPSE_SHOW = -1000000;
private final RecyclerView.OnScrollListener recyclerViewOnScrollListener = new RecyclerView.OnScrollListener() {
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
processScrollCollapse(dy);
}
@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
if (newState == RecyclerView.SCROLL_STATE_IDLE) {
View positionZero = recyclerView.getLayoutManager().findViewByPosition(0);
boolean allowHide = positionZero == null || positionZero.getTop() < 0;
if (allowHide || lastScrollDeltaOffset <= 0) {
setCollapse(lastScrollDeltaOffset <= 0 ? TOOLBAR_COLLAPSE_SHOW : TOOLBAR_COLLAPSE_HIDE, true);
} else {
setCollapse(TOOLBAR_COLLAPSE_SHOW, true);
}
}
}
};
private ImageView arrowMenuView;
private ArrowMenuDrawable arrowMenuDrawable;
@ -61,6 +85,8 @@ public class Toolbar extends LinearLayout implements View.OnClickListener, LoadV
private ToolbarCallback callback;
private NavigationItem navigationItem;
private boolean openKeyboardAfterSearchViewCreated = false;
private int lastScrollDeltaOffset;
private int scrollOffset;
public Toolbar(Context context) {
super(context);
@ -77,6 +103,39 @@ public class Toolbar extends LinearLayout implements View.OnClickListener, LoadV
init();
}
public int getToolbarHeight() {
return getHeight() == 0 ? getLayoutParams().height : getHeight();
}
public void processScrollCollapse(int offset) {
processScrollCollapse(offset, false);
}
public void processScrollCollapse(int offset, boolean animated) {
lastScrollDeltaOffset = offset;
setCollapse(offset, animated);
}
public void setCollapse(int offset, boolean animated) {
scrollOffset += offset;
scrollOffset = Math.max(0, Math.min(getHeight(), scrollOffset));
if (animated) {
animate().translationY(-scrollOffset).setDuration(300).setInterpolator(new DecelerateInterpolator(2f)).start();
} else {
animate().cancel();
setTranslationY(-scrollOffset);
}
}
public void attachRecyclerViewScrollStateListener(RecyclerView recyclerView) {
recyclerView.addOnScrollListener(recyclerViewOnScrollListener);
}
public void detachRecyclerViewScrollStateListener(RecyclerView recyclerView) {
recyclerView.removeOnScrollListener(recyclerViewOnScrollListener);
}
public void updateNavigation() {
closeSearchInternal();
setNavigationItem(false, false, navigationItem);

@ -104,6 +104,7 @@ public class AndroidUtils {
* Tries to open an app that can open the specified URL.<br>
* If this app will open the link then show a chooser to the user without this app.<br>
* Else allow the default logic to run with startActivity.
*
* @param link url to open
*/
public static void openLink(String link) {
@ -173,6 +174,10 @@ public class AndroidUtils {
return drawable;
}
public static int getDimen(Context context, int dimen) {
return context.getResources().getDimensionPixelSize(dimen);
}
public static int dp(float dp) {
return (int) (dp * getRes().getDisplayMetrics().density);
}

@ -21,12 +21,11 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
<FrameLayout
android:id="@+id/root_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?backcolor"
android:orientation="vertical">
android:background="?backcolor">
<org.floens.chan.ui.toolbar.Toolbar
android:id="@+id/toolbar"
@ -37,10 +36,9 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
<FrameLayout
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" />
android:layout_height="match_parent" />
</LinearLayout>
</FrameLayout>
<LinearLayout
android:id="@+id/drawer"

@ -15,12 +15,11 @@ GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<org.floens.chan.ui.view.TouchBlockingLinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
<org.floens.chan.ui.view.TouchBlockingFrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/root_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
android:layout_height="match_parent">
<org.floens.chan.ui.toolbar.Toolbar
android:id="@+id/toolbar"
@ -32,7 +31,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
<FrameLayout
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" />
android:layout_height="match_parent" />
</org.floens.chan.ui.view.TouchBlockingLinearLayout>
</org.floens.chan.ui.view.TouchBlockingFrameLayout>

Loading…
Cancel
Save