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