1 /*
2  * Copyright (C) 2015 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 
17 package com.android.launcher3.widget;
18 
19 import android.content.Context;
20 import android.graphics.Canvas;
21 import android.graphics.Color;
22 import android.support.v7.widget.LinearLayoutManager;
23 import android.util.AttributeSet;
24 import android.view.View;
25 import com.android.launcher3.BaseRecyclerView;
26 import com.android.launcher3.R;
27 import com.android.launcher3.model.PackageItemInfo;
28 import com.android.launcher3.model.WidgetsModel;
29 
30 /**
31  * The widgets recycler view.
32  */
33 public class WidgetsRecyclerView extends BaseRecyclerView {
34 
35     private static final String TAG = "WidgetsRecyclerView";
36     private WidgetsModel mWidgets;
37     private ScrollPositionState mScrollPosState = new ScrollPositionState();
38 
WidgetsRecyclerView(Context context)39     public WidgetsRecyclerView(Context context) {
40         this(context, null);
41     }
42 
WidgetsRecyclerView(Context context, AttributeSet attrs)43     public WidgetsRecyclerView(Context context, AttributeSet attrs) {
44         this(context, attrs, 0);
45     }
46 
WidgetsRecyclerView(Context context, AttributeSet attrs, int defStyleAttr)47     public WidgetsRecyclerView(Context context, AttributeSet attrs, int defStyleAttr) {
48         // API 21 and below only support 3 parameter ctor.
49         super(context, attrs, defStyleAttr);
50     }
51 
WidgetsRecyclerView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)52     public WidgetsRecyclerView(Context context, AttributeSet attrs, int defStyleAttr,
53             int defStyleRes) {
54         this(context, attrs, defStyleAttr);
55     }
56 
57     @Override
onFinishInflate()58     protected void onFinishInflate() {
59         super.onFinishInflate();
60         addOnItemTouchListener(this);
61     }
62 
getFastScrollerTrackColor(int defaultTrackColor)63     public int getFastScrollerTrackColor(int defaultTrackColor) {
64         return Color.WHITE;
65     }
66 
67     /**
68      * Sets the widget model in this view, used to determine the fast scroll position.
69      */
setWidgets(WidgetsModel widgets)70     public void setWidgets(WidgetsModel widgets) {
71         mWidgets = widgets;
72     }
73 
74     /**
75      * We need to override the draw to ensure that we don't draw the overscroll effect beyond the
76      * background bounds.
77      */
78     @Override
dispatchDraw(Canvas canvas)79     protected void dispatchDraw(Canvas canvas) {
80         canvas.clipRect(mBackgroundPadding.left, mBackgroundPadding.top,
81                 getWidth() - mBackgroundPadding.right,
82                 getHeight() - mBackgroundPadding.bottom);
83         super.dispatchDraw(canvas);
84     }
85 
86     /**
87      * Maps the touch (from 0..1) to the adapter position that should be visible.
88      */
89     @Override
scrollToPositionAtProgress(float touchFraction)90     public String scrollToPositionAtProgress(float touchFraction) {
91         // Skip early if widgets are not bound.
92         if (mWidgets == null) {
93             return "";
94         }
95 
96         // Skip early if there are no widgets.
97         int rowCount = mWidgets.getPackageSize();
98         if (rowCount == 0) {
99             return "";
100         }
101 
102         // Stop the scroller if it is scrolling
103         stopScroll();
104 
105         getCurScrollState(mScrollPosState, -1);
106         float pos = rowCount * touchFraction;
107         int availableScrollHeight = getAvailableScrollHeight(rowCount);
108         LinearLayoutManager layoutManager = ((LinearLayoutManager) getLayoutManager());
109         layoutManager.scrollToPositionWithOffset(0, (int) -(availableScrollHeight * touchFraction));
110 
111         int posInt = (int) ((touchFraction == 1)? pos -1 : pos);
112         PackageItemInfo p = mWidgets.getPackageItemInfo(posInt);
113         return p.titleSectionName;
114     }
115 
116     /**
117      * Updates the bounds for the scrollbar.
118      */
119     @Override
onUpdateScrollbar(int dy)120     public void onUpdateScrollbar(int dy) {
121         // Skip early if widgets are not bound.
122         if (mWidgets == null) {
123             return;
124         }
125 
126         // Skip early if there are no widgets.
127         int rowCount = mWidgets.getPackageSize();
128         if (rowCount == 0) {
129             mScrollbar.setThumbOffset(-1, -1);
130             return;
131         }
132 
133         // Skip early if, there no child laid out in the container.
134         getCurScrollState(mScrollPosState, -1);
135         if (mScrollPosState.rowIndex < 0) {
136             mScrollbar.setThumbOffset(-1, -1);
137             return;
138         }
139 
140         synchronizeScrollBarThumbOffsetToViewScroll(mScrollPosState, rowCount);
141     }
142 
143     /**
144      * Returns the current scroll state.
145      */
getCurScrollState(ScrollPositionState stateOut, int viewTypeMask)146     protected void getCurScrollState(ScrollPositionState stateOut, int viewTypeMask) {
147         stateOut.rowIndex = -1;
148         stateOut.rowTopOffset = -1;
149         stateOut.itemPos = -1;
150 
151         // Skip early if widgets are not bound.
152         if (mWidgets == null) {
153             return;
154         }
155 
156         // Return early if there are no items
157         int rowCount = mWidgets.getPackageSize();
158         if (rowCount == 0) {
159             return;
160         }
161         View child = getChildAt(0);
162         int position = getChildPosition(child);
163 
164         stateOut.rowIndex = position;
165         stateOut.rowTopOffset = getLayoutManager().getDecoratedTop(child);
166         stateOut.itemPos = position;
167     }
168 
169     @Override
getTop(int rowIndex)170     protected int getTop(int rowIndex) {
171         if (getChildCount() == 0) {
172             return 0;
173         }
174 
175         // All the rows are the same height, return any child height
176         View child = getChildAt(0);
177         return child.getMeasuredHeight() * rowIndex;
178     }
179 }