1 /*
2  * Copyright (C) 2011 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;
18 
19 import android.animation.LayoutTransition;
20 import android.content.Context;
21 import android.content.res.TypedArray;
22 import android.util.AttributeSet;
23 import android.view.LayoutInflater;
24 import android.widget.LinearLayout;
25 
26 import java.util.ArrayList;
27 
28 public class PageIndicator extends LinearLayout {
29     @SuppressWarnings("unused")
30     private static final String TAG = "PageIndicator";
31     // Want this to look good? Keep it odd
32     private static final boolean MODULATE_ALPHA_ENABLED = false;
33 
34     private LayoutInflater mLayoutInflater;
35     private int[] mWindowRange = new int[2];
36     private int mMaxWindowSize;
37 
38     private ArrayList<PageIndicatorMarker> mMarkers =
39             new ArrayList<PageIndicatorMarker>();
40     private int mActiveMarkerIndex;
41 
42     public static class PageMarkerResources {
43         int activeId;
44         int inactiveId;
45 
PageMarkerResources()46         public PageMarkerResources() {
47             activeId = R.drawable.ic_pageindicator_current;
48             inactiveId = R.drawable.ic_pageindicator_default;
49         }
PageMarkerResources(int aId, int iaId)50         public PageMarkerResources(int aId, int iaId) {
51             activeId = aId;
52             inactiveId = iaId;
53         }
54     }
55 
PageIndicator(Context context)56     public PageIndicator(Context context) {
57         this(context, null);
58     }
59 
PageIndicator(Context context, AttributeSet attrs)60     public PageIndicator(Context context, AttributeSet attrs) {
61         this(context, attrs, 0);
62     }
63 
PageIndicator(Context context, AttributeSet attrs, int defStyle)64     public PageIndicator(Context context, AttributeSet attrs, int defStyle) {
65         super(context, attrs, defStyle);
66         TypedArray a = context.obtainStyledAttributes(attrs,
67                 R.styleable.PageIndicator, defStyle, 0);
68         mMaxWindowSize = a.getInteger(R.styleable.PageIndicator_windowSize, 15);
69         mWindowRange[0] = 0;
70         mWindowRange[1] = 0;
71         mLayoutInflater = LayoutInflater.from(context);
72         a.recycle();
73 
74         // Set the layout transition properties
75         LayoutTransition transition = getLayoutTransition();
76         transition.setDuration(175);
77     }
78 
enableLayoutTransitions()79     private void enableLayoutTransitions() {
80         LayoutTransition transition = getLayoutTransition();
81         transition.enableTransitionType(LayoutTransition.APPEARING);
82         transition.enableTransitionType(LayoutTransition.DISAPPEARING);
83         transition.enableTransitionType(LayoutTransition.CHANGE_APPEARING);
84         transition.enableTransitionType(LayoutTransition.CHANGE_DISAPPEARING);
85     }
86 
disableLayoutTransitions()87     private void disableLayoutTransitions() {
88         LayoutTransition transition = getLayoutTransition();
89         transition.disableTransitionType(LayoutTransition.APPEARING);
90         transition.disableTransitionType(LayoutTransition.DISAPPEARING);
91         transition.disableTransitionType(LayoutTransition.CHANGE_APPEARING);
92         transition.disableTransitionType(LayoutTransition.CHANGE_DISAPPEARING);
93     }
94 
offsetWindowCenterTo(int activeIndex, boolean allowAnimations)95     void offsetWindowCenterTo(int activeIndex, boolean allowAnimations) {
96         if (activeIndex < 0) {
97             new Throwable().printStackTrace();
98         }
99         int windowSize = Math.min(mMarkers.size(), mMaxWindowSize);
100         int hWindowSize = (int) windowSize / 2;
101         float hfWindowSize = windowSize / 2f;
102         int windowStart = Math.max(0, activeIndex - hWindowSize);
103         int windowEnd = Math.min(mMarkers.size(), windowStart + mMaxWindowSize);
104         windowStart = windowEnd - Math.min(mMarkers.size(), windowSize);
105         int windowMid = windowStart + (windowEnd - windowStart) / 2;
106         boolean windowAtStart = (windowStart == 0);
107         boolean windowAtEnd = (windowEnd == mMarkers.size());
108         boolean windowMoved = (mWindowRange[0] != windowStart) ||
109                 (mWindowRange[1] != windowEnd);
110 
111         if (!allowAnimations) {
112             disableLayoutTransitions();
113         }
114 
115         // Remove all the previous children that are no longer in the window
116         for (int i = getChildCount() - 1; i >= 0; --i) {
117             PageIndicatorMarker marker = (PageIndicatorMarker) getChildAt(i);
118             int markerIndex = mMarkers.indexOf(marker);
119             if (markerIndex < windowStart || markerIndex >= windowEnd) {
120                 removeView(marker);
121             }
122         }
123 
124         // Add all the new children that belong in the window
125         for (int i = 0; i < mMarkers.size(); ++i) {
126             PageIndicatorMarker marker = (PageIndicatorMarker) mMarkers.get(i);
127             if (windowStart <= i && i < windowEnd) {
128                 if (indexOfChild(marker) < 0) {
129                     addView(marker, i - windowStart);
130                 }
131                 if (i == activeIndex) {
132                     marker.activate(windowMoved);
133                 } else {
134                     marker.inactivate(windowMoved);
135                 }
136             } else {
137                 marker.inactivate(true);
138             }
139 
140             if (MODULATE_ALPHA_ENABLED) {
141                 // Update the marker's alpha
142                 float alpha = 1f;
143                 if (mMarkers.size() > windowSize) {
144                     if ((windowAtStart && i > hWindowSize) ||
145                         (windowAtEnd && i < (mMarkers.size() - hWindowSize)) ||
146                         (!windowAtStart && !windowAtEnd)) {
147                         alpha = 1f - Math.abs((i - windowMid) / hfWindowSize);
148                     }
149                 }
150                 marker.animate().alpha(alpha).setDuration(500).start();
151             }
152         }
153 
154         if (!allowAnimations) {
155             enableLayoutTransitions();
156         }
157 
158         mWindowRange[0] = windowStart;
159         mWindowRange[1] = windowEnd;
160     }
161 
addMarker(int index, PageMarkerResources marker, boolean allowAnimations)162     void addMarker(int index, PageMarkerResources marker, boolean allowAnimations) {
163         index = Math.max(0, Math.min(index, mMarkers.size()));
164 
165         PageIndicatorMarker m =
166             (PageIndicatorMarker) mLayoutInflater.inflate(R.layout.page_indicator_marker,
167                     this, false);
168         m.setMarkerDrawables(marker.activeId, marker.inactiveId);
169 
170         mMarkers.add(index, m);
171         offsetWindowCenterTo(mActiveMarkerIndex, allowAnimations);
172     }
addMarkers(ArrayList<PageMarkerResources> markers, boolean allowAnimations)173     void addMarkers(ArrayList<PageMarkerResources> markers, boolean allowAnimations) {
174         for (int i = 0; i < markers.size(); ++i) {
175             addMarker(Integer.MAX_VALUE, markers.get(i), allowAnimations);
176         }
177     }
178 
updateMarker(int index, PageMarkerResources marker)179     void updateMarker(int index, PageMarkerResources marker) {
180         PageIndicatorMarker m = mMarkers.get(index);
181         m.setMarkerDrawables(marker.activeId, marker.inactiveId);
182     }
183 
removeMarker(int index, boolean allowAnimations)184     void removeMarker(int index, boolean allowAnimations) {
185         if (mMarkers.size() > 0) {
186             index = Math.max(0, Math.min(mMarkers.size() - 1, index));
187             mMarkers.remove(index);
188             offsetWindowCenterTo(mActiveMarkerIndex, allowAnimations);
189         }
190     }
removeAllMarkers(boolean allowAnimations)191     void removeAllMarkers(boolean allowAnimations) {
192         while (mMarkers.size() > 0) {
193             removeMarker(Integer.MAX_VALUE, allowAnimations);
194         }
195     }
196 
setActiveMarker(int index)197     void setActiveMarker(int index) {
198         // Center the active marker
199         mActiveMarkerIndex = index;
200         offsetWindowCenterTo(index, false);
201     }
202 
dumpState(String txt)203     void dumpState(String txt) {
204         System.out.println(txt);
205         System.out.println("\tmMarkers: " + mMarkers.size());
206         for (int i = 0; i < mMarkers.size(); ++i) {
207             PageIndicatorMarker m = mMarkers.get(i);
208             System.out.println("\t\t(" + i + ") " + m);
209         }
210         System.out.println("\twindow: [" + mWindowRange[0] + ", " + mWindowRange[1] + "]");
211         System.out.println("\tchildren: " + getChildCount());
212         for (int i = 0; i < getChildCount(); ++i) {
213             PageIndicatorMarker m = (PageIndicatorMarker) getChildAt(i);
214             System.out.println("\t\t(" + i + ") " + m);
215         }
216         System.out.println("\tactive: " + mActiveMarkerIndex);
217     }
218 }
219