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 */
16package android.support.wear.widget;
17
18import android.content.Context;
19import android.support.annotation.Nullable;
20import android.support.v7.widget.LinearLayoutManager;
21import android.support.v7.widget.RecyclerView;
22import android.view.View;
23
24/**
25 * This wear-specific implementation of {@link LinearLayoutManager} provides basic
26 * offsetting logic for updating child layout. For round devices it offsets the children
27 * horizontally to make them appear to travel around a circle. For square devices it aligns them in
28 * a straight list. This functionality is provided by the {@link CurvingLayoutCallback} which is
29 * set when constructing the this class with its default constructor
30 * {@link #WearableLinearLayoutManager(Context)}.
31 */
32public class WearableLinearLayoutManager extends LinearLayoutManager {
33
34    @Nullable
35    private LayoutCallback mLayoutCallback;
36
37    /**
38     * Callback for interacting with layout passes.
39     */
40    public abstract static class LayoutCallback {
41        /**
42         * Override this method to implement custom child layout behavior on scroll. It is called
43         * at the end of each layout pass of the view (including scrolling) and enables you to
44         * modify any property of the child view. Examples include scaling the children based on
45         * their distance from the center of the parent, or changing the translation of the children
46         * to create an illusion of the path they are moving along.
47         *
48         * @param child  the current child to be affected.
49         * @param parent the {@link RecyclerView} parent that this class is attached to.
50         */
51        public abstract void onLayoutFinished(View child, RecyclerView parent);
52    }
53
54    /**
55     * Creates a {@link WearableLinearLayoutManager} for a vertical list.
56     *
57     * @param context Current context, will be used to access resources.
58     * @param layoutCallback Callback to be associated with this {@link WearableLinearLayoutManager}
59     */
60    public WearableLinearLayoutManager(Context context, LayoutCallback layoutCallback) {
61        super(context, VERTICAL, false);
62        mLayoutCallback = layoutCallback;
63    }
64
65    /**
66     * Creates a {@link WearableLinearLayoutManager} for a vertical list.
67     *
68     * @param context Current context, will be used to access resources.
69     */
70    public WearableLinearLayoutManager(Context context) {
71        this(context, new CurvingLayoutCallback(context));
72    }
73
74    /**
75     * Set a particular instance of the layout callback for this
76     * {@link WearableLinearLayoutManager}. The callback will be called on the Ui thread.
77     *
78     * @param layoutCallback
79     */
80    public void setLayoutCallback(@Nullable LayoutCallback layoutCallback) {
81        mLayoutCallback = layoutCallback;
82    }
83
84    /**
85     * @return the current {@link LayoutCallback} associated with this
86     * {@link WearableLinearLayoutManager}.
87     */
88    @Nullable
89    public LayoutCallback getLayoutCallback() {
90        return mLayoutCallback;
91    }
92
93    @Override
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
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
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