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

Commit e16fc5c8 authored by Sunny Goyal's avatar Sunny Goyal Committed by Automerger Merge Worker
Browse files

Merge "Generalizing the PredicitonScroll view so that in can be used in...

Merge "Generalizing the PredicitonScroll view so that in can be used in all-apps" into tm-qpr-dev am: eb966492

Original change: https://googleplex-android-review.googlesource.com/c/platform/packages/apps/Launcher3/+/19089102



Change-Id: I2cfd2523cd4b9ef0f695fbd41a6293657e5b8a50
Signed-off-by: default avatarAutomerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
parents f074e39a eb966492
Loading
Loading
Loading
Loading
+5 −4
Original line number Diff line number Diff line
@@ -41,7 +41,7 @@
    </com.android.launcher3.widget.picker.WidgetPagedView>

    <!-- SearchAndRecommendationsView contains the tab layout as well -->
    <com.android.launcher3.widget.picker.SearchAndRecommendationsView
    <com.android.launcher3.views.StickyHeaderLayout
        android:id="@+id/search_and_recommendations_container"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
@@ -68,7 +68,7 @@
            android:background="?android:attr/colorBackground"
            android:paddingBottom="8dp"
            android:paddingHorizontal="@dimen/widget_list_horizontal_margin"
            android:clipToPadding="false">
            launcher:layout_sticky="true">
            <include layout="@layout/widgets_search_bar" />
        </FrameLayout>

@@ -92,7 +92,8 @@
            android:paddingLeft="@dimen/widget_tabs_horizontal_padding"
            android:paddingRight="@dimen/widget_tabs_horizontal_padding"
            android:background="?android:attr/colorBackground"
            style="@style/TextHeadline">
            style="@style/TextHeadline"
            launcher:layout_sticky="true">

            <Button
                android:id="@+id/tab_personal"
@@ -121,5 +122,5 @@
                style="?android:attr/borderlessButtonStyle" />
        </com.android.launcher3.workprofile.PersonalWorkSlidingTabStrip>

    </com.android.launcher3.widget.picker.SearchAndRecommendationsView>
    </com.android.launcher3.views.StickyHeaderLayout>
</merge>
 No newline at end of file
+6 −4
Original line number Diff line number Diff line
@@ -13,7 +13,8 @@
     See the License for the specific language governing permissions and
     limitations under the License.
-->
<merge xmlns:android="http://schemas.android.com/apk/res/android">
<merge xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:launcher="http://schemas.android.com/apk/res-auto" >
    <com.android.launcher3.widget.picker.WidgetsRecyclerView
        android:id="@+id/primary_widgets_list_view"
        android:layout_below="@id/collapse_handle"
@@ -23,7 +24,7 @@
        android:clipToPadding="false" />

    <!-- SearchAndRecommendationsView without the tab layout as well -->
    <com.android.launcher3.widget.picker.SearchAndRecommendationsView
    <com.android.launcher3.views.StickyHeaderLayout
        android:id="@+id/search_and_recommendations_container"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
@@ -50,7 +51,8 @@
            android:background="?android:attr/colorBackground"
            android:paddingHorizontal="@dimen/widget_list_horizontal_margin"
            android:paddingBottom="8dp"
            android:clipToPadding="false">
            android:clipToPadding="false"
            launcher:layout_sticky="true" >
            <include layout="@layout/widgets_search_bar" />
        </FrameLayout>

@@ -63,6 +65,6 @@
            android:paddingVertical="@dimen/recommended_widgets_table_vertical_padding"
            android:paddingHorizontal="@dimen/widget_list_horizontal_margin"
            android:visibility="gone" />
    </com.android.launcher3.widget.picker.SearchAndRecommendationsView>
    </com.android.launcher3.views.StickyHeaderLayout>

</merge>
 No newline at end of file
+4 −0
Original line number Diff line number Diff line
@@ -136,6 +136,10 @@
        <attr name="layout_ignoreInsets" format="boolean" />
    </declare-styleable>

    <declare-styleable name="StickyScroller_Layout">
        <attr name="layout_sticky" format="boolean" />
    </declare-styleable>

    <declare-styleable name="GridDisplayOption">
        <attr name="name" format="string" />

+327 −0
Original line number Diff line number Diff line
@@ -13,58 +13,55 @@
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.android.launcher3.widget.picker;
package com.android.launcher3.views;

import static android.view.View.MeasureSpec.EXACTLY;
import static android.view.View.MeasureSpec.makeMeasureSpec;

import static com.android.launcher3.anim.AnimatorListeners.forEndCallback;

import android.animation.Animator;
import android.animation.ObjectAnimator;
import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.util.FloatProperty;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import android.widget.LinearLayout;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.recyclerview.widget.RecyclerView;

import com.android.launcher3.R;
import com.android.launcher3.widget.picker.WidgetsSpaceViewHolderBinder.EmptySpaceView;
import com.android.launcher3.widget.picker.search.WidgetsSearchBar;

/**
 * A controller which measures & updates {@link WidgetsFullSheet}'s views padding, margin and
 * vertical displacement upon scrolling.
 * A {@link LinearLayout} container which allows scrolling parts of its content based on the
 * scroll of a different view. Views which are marked as sticky are not scrolled, giving the
 * illusion of a sticky header.
 */
final class SearchAndRecommendationsScrollController implements
public class StickyHeaderLayout extends LinearLayout implements
        RecyclerView.OnChildAttachStateChangeListener {

    private static final FloatProperty<SearchAndRecommendationsScrollController> SCROLL_OFFSET =
            new FloatProperty<SearchAndRecommendationsScrollController>("scrollAnimOffset") {
    private static final FloatProperty<StickyHeaderLayout> SCROLL_OFFSET =
            new FloatProperty<StickyHeaderLayout>("scrollAnimOffset") {
                @Override
        public void setValue(SearchAndRecommendationsScrollController controller, float offset) {
            controller.mScrollOffset = offset;
            controller.updateHeaderScroll();
                public void setValue(StickyHeaderLayout view, float offset) {
                    view.mScrollOffset = offset;
                    view.updateHeaderScroll();
                }

                @Override
        public Float get(SearchAndRecommendationsScrollController controller) {
            return controller.mScrollOffset;
                public Float get(StickyHeaderLayout view) {
                    return view.mScrollOffset;
                }
            };

    private static final MotionEventProxyMethod INTERCEPT_PROXY = ViewGroup::onInterceptTouchEvent;
    private static final MotionEventProxyMethod TOUCH_PROXY = ViewGroup::onTouchEvent;

    final SearchAndRecommendationsView mContainer;
    final View mSearchBarContainer;
    final WidgetsSearchBar mSearchBar;
    final TextView mHeaderTitle;
    final WidgetsRecommendationTableLayout mRecommendedWidgetsTable;
    @Nullable final View mTabBar;

    private WidgetsRecyclerView mCurrentRecyclerView;
    private RecyclerView mCurrentRecyclerView;
    private EmptySpaceView mCurrentEmptySpaceView;

    private float mLastScroll = 0;
@@ -72,22 +69,29 @@ final class SearchAndRecommendationsScrollController implements
    private Animator mOffsetAnimator;

    private boolean mShouldForwardToRecyclerView = false;

    private int mHeaderHeight;

    SearchAndRecommendationsScrollController(
            SearchAndRecommendationsView searchAndRecommendationContainer) {
        mContainer = searchAndRecommendationContainer;
        mSearchBarContainer = mContainer.findViewById(R.id.search_bar_container);
        mSearchBar = mContainer.findViewById(R.id.widgets_search_bar);
        mHeaderTitle = mContainer.findViewById(R.id.title);
        mRecommendedWidgetsTable = mContainer.findViewById(R.id.recommended_widget_table);
        mTabBar = mContainer.findViewById(R.id.tabs);
    public StickyHeaderLayout(Context context) {
        this(context, /* attrs= */ null);
    }

    public StickyHeaderLayout(Context context, AttributeSet attrs) {
        this(context, attrs, /* defStyleAttr= */ 0);
    }

    public StickyHeaderLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        this(context, attrs, defStyleAttr, /* defStyleRes= */ 0);
    }

        mContainer.setSearchAndRecommendationScrollController(this);
    public StickyHeaderLayout(Context context, AttributeSet attrs, int defStyleAttr,
            int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
    }

    public void setCurrentRecyclerView(WidgetsRecyclerView currentRecyclerView) {
    /**
     * Sets the recycler view, this sticky header should track
     */
    public void setCurrentRecyclerView(RecyclerView currentRecyclerView) {
        boolean animateReset = mCurrentRecyclerView != null;
        if (mCurrentRecyclerView != null) {
            mCurrentRecyclerView.removeOnChildAttachStateChangeListener(this);
@@ -104,16 +108,11 @@ final class SearchAndRecommendationsScrollController implements

    private void updateHeaderScroll() {
        mLastScroll = getCurrentScroll();
        mHeaderTitle.setTranslationY(mLastScroll);
        mRecommendedWidgetsTable.setTranslationY(mLastScroll);

        float searchYDisplacement = Math.max(mLastScroll, -mSearchBarContainer.getTop());
        mSearchBarContainer.setTranslationY(searchYDisplacement);

        if (mTabBar != null) {
            float tabsDisplacement = Math.max(mLastScroll, -mTabBar.getTop()
                    + mSearchBarContainer.getHeight());
            mTabBar.setTranslationY(tabsDisplacement);
        int count = getChildCount();
        for (int i = 0; i < count; i++) {
            View child = getChildAt(i);
            MyLayoutParams lp = (MyLayoutParams) child.getLayoutParams();
            child.setTranslationY(Math.max(mLastScroll, lp.scrollLimit));
        }
    }

@@ -121,25 +120,14 @@ final class SearchAndRecommendationsScrollController implements
        return mScrollOffset + (mCurrentEmptySpaceView == null ? 0 : mCurrentEmptySpaceView.getY());
    }

    /**
     * Updates the scrollable header height
     *
     * @return {@code true} if the header height or dependent property changed.
     */
    public boolean updateHeaderHeight() {
        boolean hasSizeUpdated = false;

        int headerHeight = mContainer.getMeasuredHeight();
        if (headerHeight != mHeaderHeight) {
            mHeaderHeight = headerHeight;
            hasSizeUpdated = true;
        }
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        if (mCurrentEmptySpaceView != null
                && mCurrentEmptySpaceView.setFixedHeight(mHeaderHeight)) {
            hasSizeUpdated = true;
        mHeaderHeight = getMeasuredHeight();
        if (mCurrentEmptySpaceView != null) {
            mCurrentEmptySpaceView.setFixedHeight(mHeaderHeight);
        }
        return hasSizeUpdated;
    }

    /** Resets any previous view translation. */
@@ -160,23 +148,21 @@ final class SearchAndRecommendationsScrollController implements
        }
    }

    /**
     * Returns {@code true} if a touch event should be intercepted by this controller.
     */
    @Override
    public boolean onInterceptTouchEvent(MotionEvent event) {
        return (mShouldForwardToRecyclerView = proxyMotionEvent(event, INTERCEPT_PROXY));
        return (mShouldForwardToRecyclerView = proxyMotionEvent(event, INTERCEPT_PROXY))
                || super.onInterceptTouchEvent(event);
    }

    /**
     * Returns {@code true} if this controller has intercepted and consumed a touch event.
     */
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        return mShouldForwardToRecyclerView && proxyMotionEvent(event, TOUCH_PROXY);
        return mShouldForwardToRecyclerView && proxyMotionEvent(event, TOUCH_PROXY)
                || super.onTouchEvent(event);
    }

    private boolean proxyMotionEvent(MotionEvent event, MotionEventProxyMethod method) {
        float dx = mCurrentRecyclerView.getLeft() - mContainer.getLeft();
        float dy = mCurrentRecyclerView.getTop() - mContainer.getTop();
        float dx = mCurrentRecyclerView.getLeft() - getLeft();
        float dy = mCurrentRecyclerView.getTop() - getTop();
        event.offsetLocation(dx, dy);
        try {
            return method.proxyEvent(mCurrentRecyclerView, event);
@@ -216,8 +202,126 @@ final class SearchAndRecommendationsScrollController implements
        }
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        super.onLayout(changed, l, t, r, b);

        // Update various stick parameters
        int count = getChildCount();
        int stickyHeaderHeight = 0;
        for (int i = 0; i < count; i++) {
            View v = getChildAt(i);
            MyLayoutParams lp = (MyLayoutParams) v.getLayoutParams();
            if (lp.sticky) {
                lp.scrollLimit = -v.getTop() + stickyHeaderHeight;
                stickyHeaderHeight += v.getHeight();
            } else {
                lp.scrollLimit = Integer.MIN_VALUE;
            }
        }
        updateHeaderScroll();
    }

    @Override
    protected LayoutParams generateDefaultLayoutParams() {
        return new MyLayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
    }

    @Override
    protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams lp) {
        return new MyLayoutParams(lp.width, lp.height);
    }

    @Override
    public LayoutParams generateLayoutParams(AttributeSet attrs) {
        return new MyLayoutParams(getContext(), attrs);
    }

    @Override
    protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
        return p instanceof MyLayoutParams;
    }

    private static class MyLayoutParams extends LayoutParams {

        public final boolean sticky;
        public int scrollLimit;

        MyLayoutParams(int width, int height) {
            super(width, height);
            sticky = false;
        }

        MyLayoutParams(Context c, AttributeSet attrs) {
            super(c, attrs);
            TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.StickyScroller_Layout);
            sticky = a.getBoolean(R.styleable.StickyScroller_Layout_layout_sticky, false);
            a.recycle();
        }
    }

    private interface MotionEventProxyMethod {

        boolean proxyEvent(ViewGroup view, MotionEvent event);
    }

    /**
     * Empty view which allows listening for 'Y' changes
     */
    public static class EmptySpaceView extends View {

        private Runnable mOnYChangeCallback;
        private int mHeight = 0;

        public EmptySpaceView(Context context) {
            super(context);
            animate().setUpdateListener(v -> notifyYChanged());
        }

        /**
         * Sets the height for the empty view
         * @return true if the height changed, false otherwise
         */
        public boolean setFixedHeight(int height) {
            if (mHeight != height) {
                mHeight = height;
                requestLayout();
                return true;
            }
            return false;
        }

        @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            super.onMeasure(widthMeasureSpec, makeMeasureSpec(mHeight, EXACTLY));
        }

        public void setOnYChangeCallback(Runnable callback) {
            mOnYChangeCallback = callback;
        }

        @Override
        protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
            super.onLayout(changed, left, top, right, bottom);
            notifyYChanged();
        }

        @Override
        public void offsetTopAndBottom(int offset) {
            super.offsetTopAndBottom(offset);
            notifyYChanged();
        }

        @Override
        public void setTranslationY(float translationY) {
            super.setTranslationY(translationY);
            notifyYChanged();
        }

        private void notifyYChanged() {
            if (mOnYChangeCallback != null) {
                mOnYChangeCallback.run();
            }
        }
    }
}
+0 −62
Original line number Diff line number Diff line
/*
 * Copyright (C) 2021 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.widget.picker;

import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.widget.LinearLayout;

/**
 * A {@link LinearLayout} container for holding search and widgets recommendation.
 *
 * <p>This class intercepts touch events and dispatch them to the right view.
 */
public class SearchAndRecommendationsView extends LinearLayout {
    private SearchAndRecommendationsScrollController mController;

    public SearchAndRecommendationsView(Context context) {
        this(context, /* attrs= */ null);
    }

    public SearchAndRecommendationsView(Context context, AttributeSet attrs) {
        this(context, attrs, /* defStyleAttr= */ 0);
    }

    public SearchAndRecommendationsView(Context context, AttributeSet attrs, int defStyleAttr) {
        this(context, attrs, defStyleAttr, /* defStyleRes= */ 0);
    }

    public SearchAndRecommendationsView(Context context, AttributeSet attrs, int defStyleAttr,
            int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
    }

    public void setSearchAndRecommendationScrollController(
            SearchAndRecommendationsScrollController controller) {
        mController = controller;
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent event) {
        return mController.onInterceptTouchEvent(event) || super.onInterceptTouchEvent(event);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        return mController.onTouchEvent(event) || super.onTouchEvent(event);
    }
}
Loading