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
17package com.android.launcher3;
18
19import android.animation.LayoutTransition;
20import android.content.Context;
21import android.content.res.TypedArray;
22import android.util.AttributeSet;
23import android.view.LayoutInflater;
24import android.widget.LinearLayout;
25
26import java.util.ArrayList;
27
28public 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
46        public PageMarkerResources() {
47            activeId = R.drawable.ic_pageindicator_current;
48            inactiveId = R.drawable.ic_pageindicator_default;
49        }
50        public PageMarkerResources(int aId, int iaId) {
51            activeId = aId;
52            inactiveId = iaId;
53        }
54    }
55
56    public PageIndicator(Context context) {
57        this(context, null);
58    }
59
60    public PageIndicator(Context context, AttributeSet attrs) {
61        this(context, attrs, 0);
62    }
63
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
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
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
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
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    }
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
179    void updateMarker(int index, PageMarkerResources marker) {
180        PageIndicatorMarker m = mMarkers.get(index);
181        m.setMarkerDrawables(marker.activeId, marker.inactiveId);
182    }
183
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    }
191    void removeAllMarkers(boolean allowAnimations) {
192        while (mMarkers.size() > 0) {
193            removeMarker(Integer.MAX_VALUE, allowAnimations);
194        }
195    }
196
197    void setActiveMarker(int index) {
198        // Center the active marker
199        mActiveMarkerIndex = index;
200        offsetWindowCenterTo(index, false);
201    }
202
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