Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 9480415d authored by Winson Chung's avatar Winson Chung
Browse files

Make common base class to update container bounds and to handle scroll logic.

Bug: 20763871

Change-Id: I8c2f45cfb10964e4be7e9c07c89dd336585c9989
parent 4e267f4c
Loading
Loading
Loading
Loading
+3 −2
Original line number Diff line number Diff line
@@ -50,8 +50,6 @@
    <dimen name="apps_container_width">0dp</dimen>
    <dimen name="apps_container_height">0dp</dimen>
    <dimen name="apps_container_inset">8dp</dimen>
    <!-- Note: This needs to match the fixed insets for the search box -->
    <dimen name="apps_container_fixed_bounds_inset">8dp</dimen>
    <dimen name="apps_grid_view_start_margin">52dp</dimen>
    <dimen name="apps_grid_section_y_offset">8dp</dimen>
    <dimen name="apps_view_row_height">64dp</dimen>
@@ -62,6 +60,9 @@
    <dimen name="apps_view_fast_scroll_popup_size">64dp</dimen>
    <dimen name="apps_view_fast_scroll_text_size">40dp</dimen>

    <!-- Note: This needs to match the fixed insets for the search box. -->
    <dimen name="container_fixed_bounds_inset">8dp</dimen>

<!-- AllApps/Customize/AppsCustomize -->
    <dimen name="app_icon_size">48dp</dimen>
    <dimen name="apps_customize_horizontal_padding">0dp</dimen>
+2 −38
Original line number Diff line number Diff line
@@ -30,23 +30,15 @@ import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;

import com.android.launcher3.util.Thunk;

import java.util.List;

/**
 * A RecyclerView with custom fastscroll support.  This is the main container for the all apps
 * icons.
 */
public class AppsContainerRecyclerView extends RecyclerView
        implements RecyclerView.OnItemTouchListener {
public class AppsContainerRecyclerView extends BaseContainerRecyclerView {

    private static final float FAST_SCROLL_OVERLAY_Y_OFFSET_FACTOR = 1.5f;
    private static final int SCROLL_DELTA_THRESHOLD = 4;

    /** Keeps the last known scrolling delta/velocity along y-axis. */
    @Thunk int mDy = 0;
    private float mDeltaThreshold;

    private AlphabeticalAppsList mApps;
    private int mNumAppsPerRow;
@@ -66,7 +58,6 @@ public class AppsContainerRecyclerView extends RecyclerView
    private int mScrollbarWidth;
    private int mScrollbarMinHeight;
    private int mScrollbarInset;
    private RecyclerView.OnScrollListener mScrollListenerProxy;

    public AppsContainerRecyclerView(Context context) {
        this(context, null);
@@ -100,21 +91,6 @@ public class AppsContainerRecyclerView extends RecyclerView
        mScrollbarInset =
                res.getDimensionPixelSize(R.dimen.apps_view_fast_scroll_scrubber_touch_inset);
        setFastScrollerAlpha(getFastScrollerAlpha());
        mDeltaThreshold = getResources().getDisplayMetrics().density * SCROLL_DELTA_THRESHOLD;

        ScrollListener listener = new ScrollListener();
        setOnScrollListener(listener);
    }

    private class ScrollListener extends RecyclerView.OnScrollListener {
        public ScrollListener() {
        }

        @Override
        public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
            mDy = dy;
            mScrollListenerProxy.onScrolled(recyclerView, dx, dy);
        }
    }

    /**
@@ -131,13 +107,6 @@ public class AppsContainerRecyclerView extends RecyclerView
        mNumAppsPerRow = rowSize;
    }

    /**
     * Sets an additional scroll listener, not necessary in master support lib.
     */
    public void setOnScrollListenerProxy(RecyclerView.OnScrollListener listener) {
        mScrollListenerProxy = listener;
    }

    /**
     * Sets the fast scroller alpha.
     */
@@ -187,10 +156,6 @@ public class AppsContainerRecyclerView extends RecyclerView
        handleTouchEvent(ev);
    }

    public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {
        // DO NOT REMOVE, NEEDED IMPLEMENTATION FOR M BUILDS
    }

    /**
     * Handles the touch event and determines whether to show the fast scroller (or updates it if
     * it is already showing).
@@ -206,8 +171,7 @@ public class AppsContainerRecyclerView extends RecyclerView
                // Keep track of the down positions
                mDownX = mLastX = x;
                mDownY = mLastY = y;
                if ((Math.abs(mDy) < mDeltaThreshold &&
                        getScrollState() != RecyclerView.SCROLL_STATE_IDLE)) {
                if (shouldStopScroll(ev)) {
                    stopScroll();
                }
                break;
+71 −102
Original line number Diff line number Diff line
@@ -31,7 +31,6 @@ import android.view.ViewConfiguration;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodManager;
import android.widget.EditText;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import android.widget.TextView;
import com.android.launcher3.util.Thunk;
@@ -40,10 +39,10 @@ import java.util.List;


/**
 * The all apps list view container.
 * The all apps view container.
 */
public class AppsContainerView extends FrameLayout implements DragSource, Insettable, TextWatcher,
        TextView.OnEditorActionListener, LauncherTransitionable, View.OnTouchListener,
public class AppsContainerView extends BaseContainerView implements DragSource, Insettable,
        TextWatcher, TextView.OnEditorActionListener, LauncherTransitionable, View.OnTouchListener,
        View.OnClickListener, View.OnLongClickListener {

    public static final boolean GRID_MERGE_SECTIONS = true;
@@ -73,13 +72,9 @@ public class AppsContainerView extends FrameLayout implements DragSource, Insett
    private int mNumAppsPerRow;
    private Point mLastTouchDownPos = new Point(-1, -1);
    private Point mLastTouchPos = new Point();
    private Rect mInsets = new Rect();
    private Rect mFixedBounds = new Rect();
    private int mContentMarginStart;
    // Normal container insets
    private int mContainerInset;
    // Fixed bounds container insets
    private int mFixedBoundsContainerInset;
    // RecyclerView scroll position
    @Thunk int mRecyclerViewScrollY;

@@ -99,8 +94,6 @@ public class AppsContainerView extends FrameLayout implements DragSource, Insett

        mContainerInset = context.getResources().getDimensionPixelSize(
                R.dimen.apps_container_inset);
        mFixedBoundsContainerInset = context.getResources().getDimensionPixelSize(
                R.dimen.apps_container_fixed_bounds_inset);
        mLauncher = (Launcher) context;
        mNumAppsPerRow = grid.appsViewNumCols;
        mApps = new AlphabeticalAppsList(context, mNumAppsPerRow);
@@ -146,8 +139,8 @@ public class AppsContainerView extends FrameLayout implements DragSource, Insett
     */
    public void hideHeaderBar() {
        mHeaderView.setVisibility(View.GONE);
        updateBackgrounds();
        updatePaddings();
        onUpdateBackgrounds();
        onUpdatePaddings();
    }

    /**
@@ -225,46 +218,81 @@ public class AppsContainerView extends FrameLayout implements DragSource, Insett
        if (mItemDecoration != null) {
            mAppsRecyclerView.addItemDecoration(mItemDecoration);
        }
        updateBackgrounds();
        updatePaddings();
        onUpdateBackgrounds();
        onUpdatePaddings();
    }

    @Override
    public void setInsets(Rect insets) {
        mInsets.set(insets);
        updatePaddings();
    }

    /**
     * Sets the fixed bounds for this Apps view.
     */
    public void setFixedBounds(Context context, Rect fixedBounds) {
        if (!fixedBounds.isEmpty() && !fixedBounds.equals(mFixedBounds)) {
    protected void onFixedBoundsUpdated() {
        // Update the number of items in the grid
        LauncherAppState app = LauncherAppState.getInstance();
        DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
            if (grid.updateAppsViewNumCols(context.getResources(), fixedBounds.width())) {
        if (grid.updateAppsViewNumCols(getContext().getResources(), mFixedBounds.width())) {
            mNumAppsPerRow = grid.appsViewNumCols;
            mAppsRecyclerView.setNumAppsPerRow(mNumAppsPerRow);
            mAdapter.setNumAppsPerRow(mNumAppsPerRow);
            mApps.setNumAppsPerRow(mNumAppsPerRow);
        }
    }

    /**
     * Update the padding of the Apps view and children.  To ensure that the RecyclerView has the
     * full width to handle touches right to the edge of the screen, we only apply the top and
     * bottom padding to the AppsContainerView and then the left/right padding on the RecyclerView
     * itself.  In particular, the left/right padding is applied to the background of the view,
     * and then additionally inset by the start margin.
     */
    @Override
    protected void onUpdatePaddings() {
        boolean isRtl = (getResources().getConfiguration().getLayoutDirection() ==
                LAYOUT_DIRECTION_RTL);
        boolean hasSearchBar = (mSearchBarEditView != null) &&
                (mSearchBarEditView.getVisibility() == View.VISIBLE);

            mFixedBounds.set(fixedBounds);
            if (Launcher.DISABLE_ALL_APPS_SEARCH_INTEGRATION) {
                mFixedBounds.top = mInsets.top;
                mFixedBounds.bottom = getMeasuredHeight();
        if (mFixedBounds.isEmpty()) {
            // If there are no fixed bounds, then use the default padding and insets
            setPadding(mInsets.left, mContainerInset + mInsets.top, mInsets.right,
                    mContainerInset + mInsets.bottom);
        } else {
            // If there are fixed bounds, then we update the padding to reflect the fixed bounds.
            setPadding(mFixedBounds.left, mFixedBounds.top, getMeasuredWidth() - mFixedBounds.right,
                    mInsets.bottom);
        }

        // Update the apps recycler view, inset it by the container inset as well
        int inset = mFixedBounds.isEmpty() ? mContainerInset : mFixedBoundsContainerInset;
        if (isRtl) {
            mAppsRecyclerView.setPadding(inset, inset, inset + mContentMarginStart, inset);
        } else {
            mAppsRecyclerView.setPadding(inset + mContentMarginStart, inset, inset, inset);
        }
        // Post the updates since they can trigger a relayout, and this call can be triggered from
        // a layout pass itself.
        post(new Runnable() {
            @Override
            public void run() {
                updateBackgrounds();
                updatePaddings();

        // Update the header bar
        if (hasSearchBar) {
            LinearLayout.LayoutParams lp =
                    (LinearLayout.LayoutParams) mHeaderView.getLayoutParams();
            lp.leftMargin = lp.rightMargin = inset;
        }
        });
    }

    /**
     * Update the background of the Apps view and children.
     */
    @Override
    protected void onUpdateBackgrounds() {
        int inset = mFixedBounds.isEmpty() ? mContainerInset : mFixedBoundsContainerInset;
        boolean hasSearchBar = (mSearchBarEditView != null) &&
                (mSearchBarEditView.getVisibility() == View.VISIBLE);

        // Update the background of the reveal view and list to be inset with the fixed bound
        // insets instead of the default insets
        mAppsRecyclerView.setBackground(new InsetDrawable(
                getContext().getResources().getDrawable(
                        hasSearchBar ? R.drawable.apps_list_search_bg : R.drawable.apps_list_bg),
                inset, 0, inset, 0));
        getRevealView().setBackground(new InsetDrawable(
                getContext().getResources().getDrawable(R.drawable.apps_reveal_bg),
                inset, 0, inset, 0));
    }

    @Override
@@ -530,65 +558,6 @@ public class AppsContainerView extends FrameLayout implements DragSource, Insett
        return false;
    }

    /**
     * Update the padding of the Apps view and children.  To ensure that the RecyclerView has the
     * full width to handle touches right to the edge of the screen, we only apply the top and
     * bottom padding to the AppsContainerView and then the left/right padding on the RecyclerView
     * itself.  In particular, the left/right padding is applied to the background of the view,
     * and then additionally inset by the start margin.
     */
    private void updatePaddings() {
        boolean isRtl = (getResources().getConfiguration().getLayoutDirection() ==
                LAYOUT_DIRECTION_RTL);
        boolean hasSearchBar = (mSearchBarEditView != null) &&
                (mSearchBarEditView.getVisibility() == View.VISIBLE);

        if (mFixedBounds.isEmpty()) {
            // If there are no fixed bounds, then use the default padding and insets
            setPadding(mInsets.left, mContainerInset + mInsets.top, mInsets.right,
                    mContainerInset + mInsets.bottom);
        } else {
            // If there are fixed bounds, then we update the padding to reflect the fixed bounds.
            setPadding(mFixedBounds.left, mFixedBounds.top + mFixedBoundsContainerInset,
                    getMeasuredWidth() - mFixedBounds.right,
                    mInsets.bottom + mFixedBoundsContainerInset);
        }

        // Update the apps recycler view
        int inset = mFixedBounds.isEmpty() ? mContainerInset : mFixedBoundsContainerInset;
        if (isRtl) {
            mAppsRecyclerView.setPadding(inset, inset, inset + mContentMarginStart, inset);
        } else {
            mAppsRecyclerView.setPadding(inset + mContentMarginStart, inset, inset, inset);
        }

        // Update the header
        if (hasSearchBar) {
            LinearLayout.LayoutParams lp =
                    (LinearLayout.LayoutParams) mHeaderView.getLayoutParams();
            lp.leftMargin = lp.rightMargin = inset;
        }
    }

    /**
     * Update the background of the Apps view and children.
     */
    private void updateBackgrounds() {
        int inset = mFixedBounds.isEmpty() ? mContainerInset : mFixedBoundsContainerInset;
        boolean hasSearchBar = (mSearchBarEditView != null) &&
                (mSearchBarEditView.getVisibility() == View.VISIBLE);

        // Update the background of the reveal view and list to be inset with the fixed bound
        // insets instead of the default insets
        mAppsRecyclerView.setBackground(new InsetDrawable(
                getContext().getResources().getDrawable(
                        hasSearchBar ? R.drawable.apps_list_search_bg : R.drawable.apps_list_bg),
                inset, 0, inset, 0));
        getRevealView().setBackground(new InsetDrawable(
                getContext().getResources().getDrawable(R.drawable.apps_reveal_bg),
                inset, 0, inset, 0));
    }

    /**
     * Shows the search field.
     */
+113 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2015 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.launcher3;

import android.content.Context;
import android.support.v7.widget.RecyclerView;
import android.util.AttributeSet;
import android.view.MotionEvent;
import com.android.launcher3.util.Thunk;

/**
 * A base {@link RecyclerView}, which will NOT intercept a touch sequence unless the scrolling
 * velocity is below a predefined threshold.
 */
public class BaseContainerRecyclerView extends RecyclerView
        implements RecyclerView.OnItemTouchListener {

    private static final int SCROLL_DELTA_THRESHOLD_DP = 4;

    /** Keeps the last known scrolling delta/velocity along y-axis. */
    @Thunk int mDy = 0;
    private float mDeltaThreshold;
    private RecyclerView.OnScrollListener mScrollListenerProxy;

    public BaseContainerRecyclerView(Context context) {
        this(context, null);
    }

    public BaseContainerRecyclerView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public BaseContainerRecyclerView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        mDeltaThreshold = getResources().getDisplayMetrics().density * SCROLL_DELTA_THRESHOLD_DP;

        ScrollListener listener = new ScrollListener();
        setOnScrollListener(listener);
    }

    private class ScrollListener extends OnScrollListener {
        public ScrollListener() {
            // Do nothing
        }

        @Override
        public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
            mDy = dy;
            if (mScrollListenerProxy != null) {
                mScrollListenerProxy.onScrolled(recyclerView, dx, dy);
            }
        }
    }

    /**
     * Sets an additional scroll listener, only needed for LMR1 version of the support lib.
     */
    public void setOnScrollListenerProxy(RecyclerView.OnScrollListener listener) {
        mScrollListenerProxy = listener;
    }

    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        addOnItemTouchListener(this);
    }

    @Override
    public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent ev) {
        if (shouldStopScroll(ev)) {
            stopScroll();
        }
        return false;
    }

    @Override
    public void onTouchEvent(RecyclerView rv, MotionEvent ev) {
        // Do nothing.
    }

    public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {
        // DO NOT REMOVE, NEEDED IMPLEMENTATION FOR M BUILDS
    }

    /**
     * Returns whether this {@link MotionEvent} should trigger the scroll to be stopped.
     */
    protected boolean shouldStopScroll(MotionEvent ev) {
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            if ((Math.abs(mDy) < mDeltaThreshold &&
                    getScrollState() != RecyclerView.SCROLL_STATE_IDLE)) {
                // now the touch events are being passed to the {@link WidgetCell} until the
                // touch sequence goes over the touch slop.
                return true;
            }
        }
        return false;
    }
}
 No newline at end of file
+100 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2015 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.launcher3;

import android.content.Context;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.widget.FrameLayout;

/**
 * A base container view, which supports resizing.
 */
public class BaseContainerView extends FrameLayout implements Insettable {

    protected Rect mInsets = new Rect();
    protected Rect mFixedBounds = new Rect();
    protected int mFixedBoundsContainerInset;

    public BaseContainerView(Context context) {
        this(context, null);
    }

    public BaseContainerView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public BaseContainerView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        mFixedBoundsContainerInset = context.getResources().getDimensionPixelSize(
                R.dimen.container_fixed_bounds_inset);
    }

    @Override
    final public void setInsets(Rect insets) {
        mInsets.set(insets);
        onUpdateBackgrounds();
        onUpdatePaddings();
    }

    /**
     * Sets the fixed bounds for this container view.
     */
    final public void setFixedBounds(Rect fixedBounds) {
        if (!fixedBounds.isEmpty() && !fixedBounds.equals(mFixedBounds)) {
            mFixedBounds.set(fixedBounds);
            if (Launcher.DISABLE_ALL_APPS_SEARCH_INTEGRATION) {
                mFixedBounds.top = mInsets.top;
                mFixedBounds.bottom = getMeasuredHeight();
            }
            // To ensure that the child RecyclerView has the full width to handle touches right to
            // the edge of the screen, we only apply the top and bottom padding to the bounds
            mFixedBounds.inset(0, mFixedBoundsContainerInset);
            onFixedBoundsUpdated();
        }
        // Post the updates since they can trigger a relayout, and this call can be triggered from
        // a layout pass itself.
        post(new Runnable() {
            @Override
            public void run() {
                onUpdateBackgrounds();
                onUpdatePaddings();
            }
        });
    }

    /**
     * Update the UI in response to a change in the fixed bounds.
     */
    protected void onFixedBoundsUpdated() {
        // Do nothing
    }

    /**
     * Update the paddings in response to a change in the bounds or insets.
     */
    protected void onUpdatePaddings() {
        // Do nothing
    }

    /**
     * Update the backgrounds in response to a change in the bounds or insets.
     */
    protected void onUpdateBackgrounds() {
        // Do nothing
    }
}
 No newline at end of file
Loading