1 /*
2  * Copyright (C) 2017 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package androidx.wear.widget;
17 
18 import android.content.Context;
19 import android.view.View;
20 
21 import androidx.recyclerview.widget.LinearLayoutManager;
22 import androidx.recyclerview.widget.RecyclerView;
23 
24 import org.jspecify.annotations.Nullable;
25 
26 /**
27  * This wear-specific implementation of {@link LinearLayoutManager} provides basic
28  * offsetting logic for updating child layout. For round devices it offsets the children
29  * horizontally to make them appear to travel around a circle. For square devices it aligns them in
30  * a straight list. This functionality is provided by the {@link CurvingLayoutCallback} which is
31  * set when constructing the this class with its default constructor
32  * {@link #WearableLinearLayoutManager(Context)}.
33  */
34 public class WearableLinearLayoutManager extends LinearLayoutManager {
35 
36     private @Nullable LayoutCallback mLayoutCallback;
37 
38     /**
39      * Callback for interacting with layout passes.
40      */
41     public abstract static class LayoutCallback {
42         /**
43          * Override this method to implement custom child layout behavior on scroll. It is called
44          * at the end of each layout pass of the view (including scrolling) and enables you to
45          * modify any property of the child view. Examples include scaling the children based on
46          * their distance from the center of the parent, or changing the translation of the children
47          * to create an illusion of the path they are moving along.
48          *
49          * @param child  the current child to be affected.
50          * @param parent the {@link RecyclerView} parent that this class is attached to.
51          */
onLayoutFinished(View child, RecyclerView parent)52         public abstract void onLayoutFinished(View child, RecyclerView parent);
53     }
54 
55     /**
56      * Creates a {@link WearableLinearLayoutManager} for a vertical list.
57      *
58      * @param context Current context, will be used to access resources.
59      * @param layoutCallback Callback to be associated with this {@link WearableLinearLayoutManager}
60      */
WearableLinearLayoutManager(Context context, LayoutCallback layoutCallback)61     public WearableLinearLayoutManager(Context context, LayoutCallback layoutCallback) {
62         super(context, VERTICAL, false);
63         mLayoutCallback = layoutCallback;
64     }
65 
66     /**
67      * Creates a {@link WearableLinearLayoutManager} for a vertical list.
68      *
69      * @param context Current context, will be used to access resources.
70      */
WearableLinearLayoutManager(Context context)71     public WearableLinearLayoutManager(Context context) {
72         this(context, new CurvingLayoutCallback(context));
73     }
74 
75     /**
76      * Set a particular instance of the layout callback for this
77      * {@link WearableLinearLayoutManager}. The callback will be called on the Ui thread.
78      *
79      * @param layoutCallback
80      */
setLayoutCallback(@ullable LayoutCallback layoutCallback)81     public void setLayoutCallback(@Nullable LayoutCallback layoutCallback) {
82         mLayoutCallback = layoutCallback;
83     }
84 
85     /**
86      * @return the current {@link LayoutCallback} associated with this
87      * {@link WearableLinearLayoutManager}.
88      */
getLayoutCallback()89     public @Nullable LayoutCallback getLayoutCallback() {
90         return mLayoutCallback;
91     }
92 
93     @Override
scrollVerticallyBy( int dy, RecyclerView.Recycler recycler, RecyclerView.State state)94     public int scrollVerticallyBy(
95             int dy, RecyclerView.Recycler recycler, RecyclerView.State state) {
96         int scrolled = super.scrollVerticallyBy(dy, recycler, state);
97 
98         updateLayout();
99         return scrolled;
100     }
101 
102     @Override
onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state)103     public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
104         super.onLayoutChildren(recycler, state);
105         if (getChildCount() == 0) {
106             return;
107         }
108 
109         updateLayout();
110     }
111 
updateLayout()112     private void updateLayout() {
113         if (mLayoutCallback == null) {
114             return;
115         }
116         final int childCount = getChildCount();
117         for (int count = 0; count < childCount; count++) {
118             View child = getChildAt(count);
119             mLayoutCallback.onLayoutFinished(child, (WearableRecyclerView) child.getParent());
120         }
121     }
122 }
123